use crate::{FromBytes, ToBytes, io_error};
use enum_iterator::{Sequence, last};
use std::io;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Sequence)]
#[repr(u16)]
pub enum ConsensusVersion {
V1 = 1,
V2 = 2,
V3 = 3,
V4 = 4,
V5 = 5,
V6 = 6,
V7 = 7,
V8 = 8,
V9 = 9,
V10 = 10,
V11 = 11,
V12 = 12,
V13 = 13,
V14 = 14,
}
impl ToBytes for ConsensusVersion {
fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
(*self as u16).write_le(writer)
}
}
impl FromBytes for ConsensusVersion {
fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
match u16::read_le(reader)? {
0 => Err(io_error("Zero is not a valid consensus version")),
1 => Ok(Self::V1),
2 => Ok(Self::V2),
3 => Ok(Self::V3),
4 => Ok(Self::V4),
5 => Ok(Self::V5),
6 => Ok(Self::V6),
7 => Ok(Self::V7),
8 => Ok(Self::V8),
9 => Ok(Self::V9),
10 => Ok(Self::V10),
11 => Ok(Self::V11),
12 => Ok(Self::V12),
13 => Ok(Self::V13),
14 => Ok(Self::V14),
_ => Err(io_error("Invalid consensus version")),
}
}
}
impl ConsensusVersion {
pub fn latest() -> Self {
last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
}
}
impl std::fmt::Display for ConsensusVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
pub(crate) const NUM_CONSENSUS_VERSIONS: usize = enum_iterator::cardinality::<ConsensusVersion>();
pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
(ConsensusVersion::V1, 0),
(ConsensusVersion::V2, 2_900_000),
(ConsensusVersion::V3, 4_560_000),
(ConsensusVersion::V4, 5_730_000),
(ConsensusVersion::V5, 5_780_000),
(ConsensusVersion::V6, 6_240_000),
(ConsensusVersion::V7, 6_880_000),
(ConsensusVersion::V8, 7_565_000),
(ConsensusVersion::V9, 8_028_000),
(ConsensusVersion::V10, 8_600_000),
(ConsensusVersion::V11, 9_510_000),
(ConsensusVersion::V12, 10_030_000),
(ConsensusVersion::V13, 10_881_000),
(ConsensusVersion::V14, 11_960_000),
];
pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
(ConsensusVersion::V1, 0),
(ConsensusVersion::V2, 2_800_000),
(ConsensusVersion::V3, 4_900_000),
(ConsensusVersion::V4, 6_135_000),
(ConsensusVersion::V5, 7_060_000),
(ConsensusVersion::V6, 7_560_000),
(ConsensusVersion::V7, 7_570_000),
(ConsensusVersion::V8, 9_430_000),
(ConsensusVersion::V9, 10_272_000),
(ConsensusVersion::V10, 11_205_000),
(ConsensusVersion::V11, 12_870_000),
(ConsensusVersion::V12, 13_815_000),
(ConsensusVersion::V13, 16_850_000),
(ConsensusVersion::V14, 17_700_000),
];
pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
(ConsensusVersion::V1, 0),
(ConsensusVersion::V2, 2_950_000),
(ConsensusVersion::V3, 4_800_000),
(ConsensusVersion::V4, 6_625_000),
(ConsensusVersion::V5, 6_765_000),
(ConsensusVersion::V6, 7_600_000),
(ConsensusVersion::V7, 8_365_000),
(ConsensusVersion::V8, 9_173_000),
(ConsensusVersion::V9, 9_800_000),
(ConsensusVersion::V10, 10_525_000),
(ConsensusVersion::V11, 11_952_000),
(ConsensusVersion::V12, 12_669_000),
(ConsensusVersion::V13, 14_906_000),
(ConsensusVersion::V14, 15_370_000),
];
pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
(ConsensusVersion::V1, 0),
(ConsensusVersion::V2, 5),
(ConsensusVersion::V3, 6),
(ConsensusVersion::V4, 7),
(ConsensusVersion::V5, 8),
(ConsensusVersion::V6, 9),
(ConsensusVersion::V7, 10),
(ConsensusVersion::V8, 11),
(ConsensusVersion::V9, 12),
(ConsensusVersion::V10, 13),
(ConsensusVersion::V11, 14),
(ConsensusVersion::V12, 15),
(ConsensusVersion::V13, 16),
(ConsensusVersion::V14, 17),
];
#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
load_test_consensus_heights_inner(std::env::var("CONSENSUS_VERSION_HEIGHTS").ok())
}
#[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
pub(crate) fn load_test_consensus_heights_inner(
consensus_version_heights: Option<String>,
) -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| {
assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
for window in heights.windows(2) {
if window[0] >= window[1] {
panic!("Heights must be strictly increasing, but found: {window:?}");
}
}
};
let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
match consensus_version_heights {
Some(height_string) => {
let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
.replace(" ", "")
.split(",")
.map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
.collect::<Vec<u32>>()
.try_into()
.expect(&parsing_error);
for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
}
verify_consensus_heights(&test_consensus_heights);
test_consensus_heights
}
None => {
verify_consensus_heights(&test_consensus_heights);
test_consensus_heights
}
}
}
#[macro_export]
macro_rules! consensus_config_value {
($network:ident, $constant:ident, $seek_height:expr) => {
$network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
Ok(index) => Some($network::$constant[index].1),
Err(index) => {
if index == 0 {
None
} else {
Some($network::$constant[index - 1].1)
}
}
}
})
};
}
#[macro_export]
macro_rules! consensus_config_value_by_version {
($network:ident, $constant:ident, $seek_version:expr) => {
match $network::$constant.binary_search_by(|(version, _)| version.cmp(&$seek_version)) {
Ok(index) => Some($network::$constant[index].1),
Err(index) => {
if index == 0 {
None
} else {
Some($network::$constant[index - 1].1)
}
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
fn consensus_constants_at_genesis<N: Network>() {
let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
assert_eq!(height, 0);
let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
assert_eq!(consensus_version, ConsensusVersion::V1);
assert_eq!(consensus_version as usize, 1);
}
fn consensus_versions<N: Network>() {
let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
assert_eq!(previous_version as usize, 1);
for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
assert_eq!(*version as usize, previous_version as usize + 1);
previous_version = *version;
}
let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
assert!(*version > previous_version);
previous_version = *version;
}
let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
assert!(*version > previous_version);
previous_version = *version;
}
let mut previous_version = N::MAX_ARRAY_ELEMENTS.first().unwrap().0;
for (version, _) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
assert!(*version > previous_version);
previous_version = *version;
}
let mut previous_version = N::MAX_PROGRAM_SIZE.first().unwrap().0;
for (version, _) in N::MAX_PROGRAM_SIZE.iter().skip(1) {
assert!(*version > previous_version);
previous_version = *version;
}
let mut previous_version = N::MAX_TRANSACTION_SIZE.first().unwrap().0;
for (version, _) in N::MAX_TRANSACTION_SIZE.iter().skip(1) {
assert!(*version > previous_version);
previous_version = *version;
}
let mut previous_version = N::MAX_WRITES.first().unwrap().0;
for (version, _) in N::MAX_WRITES.iter().skip(1) {
assert!(*version > previous_version);
previous_version = *version;
}
}
fn consensus_constants_increasing_heights<N: Network>() {
let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
assert!(*height > previous_height);
previous_height = *height;
assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
}
}
fn consensus_constants_valid_heights<N: Network>() {
for (version, value) in N::MAX_CERTIFICATES.iter() {
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
}
for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
}
for (version, value) in N::MAX_ARRAY_ELEMENTS.iter() {
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
assert_eq!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, height).unwrap(), *value);
}
for (version, value) in N::MAX_PROGRAM_SIZE.iter() {
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
assert_eq!(consensus_config_value!(N, MAX_PROGRAM_SIZE, height).unwrap(), *value);
}
for (version, value) in N::MAX_TRANSACTION_SIZE.iter() {
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
assert_eq!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, height).unwrap(), *value);
}
for (version, value) in N::MAX_WRITES.iter() {
let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
assert_eq!(consensus_config_value!(N, MAX_WRITES, height).unwrap(), *value);
}
}
fn consensus_config_returns_some<N: Network>() {
for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
assert!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, *height).is_some());
assert!(consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).is_some());
assert!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).is_some());
assert!(consensus_config_value!(N, MAX_WRITES, *height).is_some());
}
}
fn max_certificates_increasing<N: Network>() {
let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
for (_, value) in N::MAX_CERTIFICATES.iter().skip(1) {
assert!(*value >= previous_value);
previous_value = *value;
}
}
fn max_array_elements_increasing<N: Network>() {
let mut previous_value = N::MAX_ARRAY_ELEMENTS.first().unwrap().1;
for (_, value) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
assert!(*value >= previous_value);
previous_value = *value;
}
}
fn transaction_size_exceeds_program_size<N: Network>() {
const MIN_OVERHEAD: usize = 28_000;
for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
let max_program_size = consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).unwrap();
let max_transaction_size = consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).unwrap();
assert!(
max_transaction_size >= max_program_size + MIN_OVERHEAD,
"At height {height}: MAX_TRANSACTION_SIZE ({max_transaction_size}) must be at least {MIN_OVERHEAD} bytes greater than MAX_PROGRAM_SIZE ({max_program_size})"
);
}
}
fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
let _ = [N1::MAX_ARRAY_ELEMENTS, N2::MAX_ARRAY_ELEMENTS, N3::MAX_ARRAY_ELEMENTS];
let _ = [N1::MAX_PROGRAM_SIZE, N2::MAX_PROGRAM_SIZE, N3::MAX_PROGRAM_SIZE];
let _ = [N1::MAX_TRANSACTION_SIZE, N2::MAX_TRANSACTION_SIZE, N3::MAX_TRANSACTION_SIZE];
let _ = [N1::MAX_WRITES, N2::MAX_WRITES, N3::MAX_WRITES];
}
fn latest_max_functions_are_safe<N: Network>() {
assert!(N::LATEST_MAX_CERTIFICATES() > 0, "LATEST_MAX_CERTIFICATES must be positive");
assert!(N::LATEST_MAX_PROGRAM_SIZE() > 0, "LATEST_MAX_PROGRAM_SIZE must be positive");
assert!(N::LATEST_MAX_TRANSACTION_SIZE() > 0, "LATEST_MAX_TRANSACTION_SIZE must be positive");
assert!(N::LATEST_MAX_WRITES() > 0, "LATEST_MAX_WRITES must be positive");
}
#[test]
#[allow(clippy::assertions_on_constants)]
fn test_consensus_constants() {
consensus_constants_at_genesis::<MainnetV0>();
consensus_constants_at_genesis::<TestnetV0>();
consensus_constants_at_genesis::<CanaryV0>();
consensus_versions::<MainnetV0>();
consensus_versions::<TestnetV0>();
consensus_versions::<CanaryV0>();
consensus_constants_increasing_heights::<MainnetV0>();
consensus_constants_increasing_heights::<TestnetV0>();
consensus_constants_increasing_heights::<CanaryV0>();
consensus_constants_valid_heights::<MainnetV0>();
consensus_constants_valid_heights::<TestnetV0>();
consensus_constants_valid_heights::<CanaryV0>();
consensus_config_returns_some::<MainnetV0>();
consensus_config_returns_some::<TestnetV0>();
consensus_config_returns_some::<CanaryV0>();
max_certificates_increasing::<MainnetV0>();
max_certificates_increasing::<TestnetV0>();
max_certificates_increasing::<CanaryV0>();
max_array_elements_increasing::<MainnetV0>();
max_array_elements_increasing::<TestnetV0>();
max_array_elements_increasing::<CanaryV0>();
transaction_size_exceeds_program_size::<MainnetV0>();
transaction_size_exceeds_program_size::<TestnetV0>();
transaction_size_exceeds_program_size::<CanaryV0>();
latest_max_functions_are_safe::<MainnetV0>();
latest_max_functions_are_safe::<TestnetV0>();
latest_max_functions_are_safe::<CanaryV0>();
constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
}
#[test]
fn test_to_bytes() {
let version = ConsensusVersion::V8;
let bytes = version.to_bytes_le().unwrap();
let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
assert_eq!(result, version);
let version = ConsensusVersion::latest();
let bytes = version.to_bytes_le().unwrap();
let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
assert_eq!(result, version);
let invalid_bytes = u16::MAX.to_bytes_le().unwrap();
let result = ConsensusVersion::from_bytes_le(&invalid_bytes);
assert!(result.is_err());
}
}