//! Consensus parameters. use std::cmp::{Ord, Ordering}; use std::convert::TryFrom; use std::fmt; use std::ops::{Add, Sub}; use crate::constants; #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct BlockHeight(u32); pub const H0: BlockHeight = BlockHeight(0); impl BlockHeight { pub const fn from_u32(v: u32) -> BlockHeight { BlockHeight(v) } } impl fmt::Display for BlockHeight { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(formatter) } } impl Ord for BlockHeight { fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } impl PartialOrd for BlockHeight { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl From for BlockHeight { fn from(value: u32) -> Self { BlockHeight(value) } } impl From for BlockHeight { fn from(value: u64) -> Self { BlockHeight(value as u32) } } impl TryFrom for BlockHeight { type Error = std::num::TryFromIntError; fn try_from(value: i32) -> Result { u32::try_from(value).map(BlockHeight) } } impl TryFrom for BlockHeight { type Error = std::num::TryFromIntError; fn try_from(value: i64) -> Result { u32::try_from(value).map(BlockHeight) } } impl From for u32 { fn from(value: BlockHeight) -> u32 { value.0 } } impl From for u64 { fn from(value: BlockHeight) -> u64 { value.0 as u64 } } impl From for i64 { fn from(value: BlockHeight) -> i64 { value.0 as i64 } } impl Add for BlockHeight { type Output = Self; fn add(self, other: u32) -> Self { BlockHeight(self.0 + other) } } impl Add for BlockHeight { type Output = Self; fn add(self, other: Self) -> Self { self + other.0 } } impl Sub for BlockHeight { type Output = Self; fn sub(self, other: u32) -> Self { if other > self.0 { panic!("Subtraction resulted in negative block height."); } BlockHeight(self.0 - other) } } impl Sub for BlockHeight { type Output = Self; fn sub(self, other: Self) -> Self { self - other.0 } } /// Zcash consensus parameters. pub trait Parameters { fn activation_height(&self, nu: NetworkUpgrade) -> Option; fn hrp_sapling_extended_full_viewing_key(&self) -> &str; fn hrp_sapling_payment_address(&self) -> &str; fn b58_pubkey_address_prefix(&self) -> [u8; 2]; fn b58_script_address_prefix(&self) -> [u8; 2]; fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { match self.activation_height(nu) { Some(h) if h <= height => true, _ => false, } } } /// Marker struct for the production network. #[derive(Clone, Copy, Debug)] pub enum Network { MainNetwork, TestNetwork, } impl Parameters for Network { fn activation_height(&self, nu: NetworkUpgrade) -> Option { match self { Network::MainNetwork => match nu { NetworkUpgrade::Overwinter => Some(BlockHeight(347_500)), NetworkUpgrade::Sapling => Some(BlockHeight(419_200)), NetworkUpgrade::Blossom => Some(BlockHeight(653_600)), NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)), NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)), }, Network::TestNetwork => match nu { NetworkUpgrade::Overwinter => Some(BlockHeight(207_500)), NetworkUpgrade::Sapling => Some(BlockHeight(280_000)), NetworkUpgrade::Blossom => Some(BlockHeight(584_000)), NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)), NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)), }, } } fn hrp_sapling_extended_full_viewing_key(&self) -> &str { match self { Network::MainNetwork => constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, Network::TestNetwork => constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, } } fn hrp_sapling_payment_address(&self) -> &str { match self { Network::MainNetwork => constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS, Network::TestNetwork => constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, } } fn b58_pubkey_address_prefix(&self) -> [u8; 2] { match self { Network::MainNetwork => constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX, Network::TestNetwork => constants::testnet::B58_PUBKEY_ADDRESS_PREFIX, } } fn b58_script_address_prefix(&self) -> [u8; 2] { match self { Network::MainNetwork => constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX, Network::TestNetwork => constants::testnet::B58_SCRIPT_ADDRESS_PREFIX, } } } /// An event that occurs at a specified height on the Zcash chain, at which point the /// consensus rules enforced by the network are altered. /// /// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. #[derive(Clone, Copy, Debug)] pub enum NetworkUpgrade { /// The [Overwinter] network upgrade. /// /// [Overwinter]: https://z.cash/upgrade/overwinter/ Overwinter, /// The [Sapling] network upgrade. /// /// [Sapling]: https://z.cash/upgrade/sapling/ Sapling, /// The [Blossom] network upgrade. /// /// [Blossom]: https://z.cash/upgrade/blossom/ Blossom, /// The [Heartwood] network upgrade. /// /// [Heartwood]: https://z.cash/upgrade/heartwood/ Heartwood, /// The [Canopy] network upgrade. /// /// [Canopy]: https://z.cash/upgrade/canopy/ Canopy, } impl fmt::Display for NetworkUpgrade { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { NetworkUpgrade::Overwinter => write!(f, "Overwinter"), NetworkUpgrade::Sapling => write!(f, "Sapling"), NetworkUpgrade::Blossom => write!(f, "Blossom"), NetworkUpgrade::Heartwood => write!(f, "Heartwood"), NetworkUpgrade::Canopy => write!(f, "Canopy"), } } } impl NetworkUpgrade { fn branch_id(self) -> BranchId { match self { NetworkUpgrade::Overwinter => BranchId::Overwinter, NetworkUpgrade::Sapling => BranchId::Sapling, NetworkUpgrade::Blossom => BranchId::Blossom, NetworkUpgrade::Heartwood => BranchId::Heartwood, NetworkUpgrade::Canopy => BranchId::Canopy, } } } /// The network upgrades on the Zcash chain in order of activation. /// /// This order corresponds to the activation heights, but because Rust enums are /// full-fledged algebraic data types, we need to define it manually. const UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[ NetworkUpgrade::Overwinter, NetworkUpgrade::Sapling, NetworkUpgrade::Blossom, NetworkUpgrade::Heartwood, NetworkUpgrade::Canopy, ]; pub const ZIP212_GRACE_PERIOD: u32 = 32256; /// A globally-unique identifier for a set of consensus rules within the Zcash chain. /// /// Each branch ID in this enum corresponds to one of the epochs between a pair of Zcash /// network upgrades. For example, `BranchId::Overwinter` corresponds to the blocks /// starting at Overwinter activation, and ending the block before Sapling activation. /// /// The main use of the branch ID is in signature generation: transactions commit to a /// specific branch ID by including it as part of [`signature_hash`]. This ensures /// two-way replay protection for transactions across network upgrades. /// /// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. /// /// [`signature_hash`]: crate::transaction::signature_hash #[derive(Clone, Copy, Debug, PartialEq)] pub enum BranchId { /// The consensus rules at the launch of Zcash. Sprout, /// The consensus rules deployed by [`NetworkUpgrade::Overwinter`]. Overwinter, /// The consensus rules deployed by [`NetworkUpgrade::Sapling`]. Sapling, /// The consensus rules deployed by [`NetworkUpgrade::Blossom`]. Blossom, /// The consensus rules deployed by [`NetworkUpgrade::Heartwood`]. Heartwood, /// The consensus rules deployed by [`NetworkUpgrade::Canopy`]. Canopy, } impl TryFrom for BranchId { type Error = &'static str; fn try_from(value: u32) -> Result { match value { 0 => Ok(BranchId::Sprout), 0x5ba8_1b19 => Ok(BranchId::Overwinter), 0x76b8_09bb => Ok(BranchId::Sapling), 0x2bb4_0e60 => Ok(BranchId::Blossom), 0xf5b9_230b => Ok(BranchId::Heartwood), 0xe9ff_75a6 => Ok(BranchId::Canopy), _ => Err("Unknown consensus branch ID"), } } } impl From for u32 { fn from(consensus_branch_id: BranchId) -> u32 { match consensus_branch_id { BranchId::Sprout => 0, BranchId::Overwinter => 0x5ba8_1b19, BranchId::Sapling => 0x76b8_09bb, BranchId::Blossom => 0x2bb4_0e60, BranchId::Heartwood => 0xf5b9_230b, BranchId::Canopy => 0xe9ff_75a6, } } } impl BranchId { /// Returns the branch ID corresponding to the consensus rule set that is active at /// the given height. /// /// This is the branch ID that should be used when creating transactions. pub fn for_height(parameters: &P, height: BlockHeight) -> Self { for nu in UPGRADES_IN_ORDER.iter().rev() { if parameters.is_nu_active(*nu, height) { return nu.branch_id(); } } // Sprout rules apply before any network upgrade BranchId::Sprout } } #[cfg(test)] mod tests { use std::convert::TryFrom; use super::{BlockHeight, BranchId, Network, NetworkUpgrade, Parameters, UPGRADES_IN_ORDER}; #[test] fn nu_ordering() { for i in 1..UPGRADES_IN_ORDER.len() { let nu_a = UPGRADES_IN_ORDER[i - 1]; let nu_b = UPGRADES_IN_ORDER[i]; match ( Network::MainNetwork.activation_height(nu_a), Network::MainNetwork.activation_height(nu_b), ) { (Some(a), Some(b)) if a < b => (), (Some(_), None) => (), (None, None) => (), _ => panic!( "{} should not be before {} in UPGRADES_IN_ORDER", nu_a, nu_b ), } } } #[test] fn nu_is_active() { assert!(!Network::MainNetwork.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(0))); assert!( !Network::MainNetwork.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_499)) ); assert!(Network::MainNetwork.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_500))); } #[test] fn branch_id_from_u32() { assert_eq!(BranchId::try_from(0), Ok(BranchId::Sprout)); assert!(BranchId::try_from(1).is_err()); } #[test] fn branch_id_for_height() { assert_eq!( BranchId::for_height(&Network::MainNetwork, BlockHeight(0)), BranchId::Sprout, ); assert_eq!( BranchId::for_height(&Network::MainNetwork, BlockHeight(419_199)), BranchId::Overwinter, ); assert_eq!( BranchId::for_height(&Network::MainNetwork, BlockHeight(419_200)), BranchId::Sapling, ); assert_eq!( BranchId::for_height(&Network::MainNetwork, BlockHeight(903_000)), BranchId::Heartwood, ); assert_eq!( BranchId::for_height(&Network::MainNetwork, BlockHeight(1_046_400)), BranchId::Canopy, ); assert_eq!( BranchId::for_height(&Network::MainNetwork, BlockHeight(5_000_000)), BranchId::Canopy, ); } }