//! Consensus logic and parameters. use core::cmp::{Ord, Ordering}; use core::convert::TryFrom; use core::fmt; use core::num::TryFromIntError; use core::ops::{Add, Bound, RangeBounds, Sub}; #[cfg(feature = "std")] use memuse::DynamicUsage; use crate::constants::{mainnet, regtest, testnet}; /// A wrapper type representing blockchain heights. /// /// Safe conversion from various integer types, as well as addition and subtraction, are provided. /// Subtraction of block heights, and of deltas to block heights, are always saturating. #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct BlockHeight(u32); #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(BlockHeight); /// The height of the genesis block on a network. pub const H0: BlockHeight = BlockHeight(0); /// The target time between blocks, in seconds (the post-Blossom target spacing). pub const SECONDS_PER_BLOCK: u32 = 75; /// The approximate number of blocks produced per hour at [`SECONDS_PER_BLOCK`]. pub const BLOCKS_PER_HOUR: u32 = 3600 / SECONDS_PER_BLOCK; impl BlockHeight { pub const fn from_u32(v: u32) -> BlockHeight { BlockHeight(v) } /// Subtracts the provided value from this height, returning [`H0`] if this would result in /// underflow of the wrapped `u32`. pub fn saturating_sub(self, v: u32) -> BlockHeight { BlockHeight(self.0.saturating_sub(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 u32 { fn from(value: BlockHeight) -> u32 { value.0 } } impl TryFrom for BlockHeight { type Error = core::num::TryFromIntError; fn try_from(value: u64) -> Result { u32::try_from(value).map(BlockHeight) } } impl From for u64 { fn from(value: BlockHeight) -> u64 { u64::from(value.0) } } impl TryFrom for BlockHeight { type Error = core::num::TryFromIntError; fn try_from(value: i32) -> Result { u32::try_from(value).map(BlockHeight) } } impl TryFrom for BlockHeight { type Error = core::num::TryFromIntError; fn try_from(value: i64) -> Result { u32::try_from(value).map(BlockHeight) } } impl From for i64 { fn from(value: BlockHeight) -> i64 { i64::from(value.0) } } impl Add for BlockHeight { type Output = Self; fn add(self, other: u32) -> Self { BlockHeight(self.0.saturating_add(other)) } } impl Sub for BlockHeight { type Output = Self; fn sub(self, other: u32) -> Self { BlockHeight(self.0.saturating_sub(other)) } } impl Sub for BlockHeight { type Output = u32; fn sub(self, other: BlockHeight) -> u32 { self.0.saturating_sub(other.0) } } /// A wrapper type the index of a transaction within a block. /// /// Safe conversion from various integer types are provided. #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct TxIndex(u16); impl From for u16 { fn from(value: TxIndex) -> Self { value.0 } } impl From for u32 { fn from(value: TxIndex) -> Self { value.0.into() } } impl From for u64 { fn from(value: TxIndex) -> Self { value.0.into() } } impl From for TxIndex { fn from(value: u16) -> Self { TxIndex(value) } } impl TryFrom for TxIndex { type Error = TryFromIntError; fn try_from(value: u32) -> Result { Ok(TxIndex(u16::try_from(value)?)) } } impl TryFrom for TxIndex { type Error = TryFromIntError; fn try_from(value: u64) -> Result { Ok(TxIndex(u16::try_from(value)?)) } } impl TryFrom for TxIndex { type Error = TryFromIntError; fn try_from(value: usize) -> Result { Ok(TxIndex(u16::try_from(value)?)) } } #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(TxIndex); /// The enumeration of known Zcash network types. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NetworkType { /// Zcash Mainnet. Main, /// Zcash Testnet. Test, /// Private integration / regression testing, used in `zcashd`. /// /// For some address types there is no distinction between test and regtest encodings; /// those will always be parsed as `Network::Test`. Regtest, } #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(NetworkType); pub(crate) mod private { pub trait Sealed {} } /// Constants associated with a given Zcash network. pub trait NetworkConstants: private::Sealed + Clone { /// The coin type for ZEC, as defined by [SLIP 44]. /// /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md fn coin_type(&self) -> u32; /// Returns the human-readable prefix for Bech32-encoded Sapling extended spending keys /// for the network to which this NetworkConstants value applies. /// /// Defined in [ZIP 32]. /// /// [ZIP 32]: https://github.com/zcash/zips/blob/main/zips/zip-0032.rst #[deprecated( since = "0.11.0", note = "Use `zcash_keys::constants::hrp_sapling_extended_spending_key` instead." )] fn hrp_sapling_extended_spending_key(&self) -> &'static str; /// Returns the human-readable prefix for Bech32-encoded Sapling extended full /// viewing keys for the network to which this NetworkConstants value applies. /// /// Defined in [ZIP 32]. /// /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst #[deprecated( since = "0.11.0", note = "Use `zcash_keys::constants::hrp_sapling_extended_full_viewing_key` instead." )] fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str; /// Returns the Bech32-encoded human-readable prefix for Sapling payment addresses /// for the network to which this NetworkConstants value applies. /// /// Defined in section 5.6.4 of the [Zcash Protocol Specification]. /// /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/main/rendered/protocol/protocol.pdf #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::hrp_sapling_payment_address` instead." )] fn hrp_sapling_payment_address(&self) -> &'static str; /// Returns the human-readable prefix for Base58Check-encoded Sprout /// payment addresses for the network to which this NetworkConstants value /// applies. /// /// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. /// /// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::b58_sprout_address_prefix` instead." )] fn b58_sprout_address_prefix(&self) -> [u8; 2]; /// Returns the human-readable prefix for Base58Check-encoded transparent /// pay-to-public-key-hash payment addresses for the network to which this NetworkConstants value /// applies. #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::b58_pubkey_address_prefix` instead." )] fn b58_pubkey_address_prefix(&self) -> [u8; 2]; /// Returns the human-readable prefix for Base58Check-encoded transparent secret key for the /// network to which this NetworkConstants value applies. #[deprecated( since = "0.11.0", note = "Use `zcash_keys::constants::b58_secret_key_prefix` instead." )] fn b58_secret_key_prefix(&self) -> [u8; 1]; /// Returns the human-readable prefix for Base58Check-encoded transparent pay-to-script-hash /// payment addresses for the network to which this NetworkConstants value applies. #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::b58_script_address_prefix` instead." )] fn b58_script_address_prefix(&self) -> [u8; 2]; /// Returns the Bech32-encoded human-readable prefix for TEX addresses, for the /// network to which this `NetworkConstants` value applies. /// /// Defined in [ZIP 320]. /// /// [ZIP 320]: https://zips.z.cash/zip-0320 #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::hrp_tex_address` instead." )] fn hrp_tex_address(&self) -> &'static str; /// The HRP for a Bech32m-encoded mainnet Unified Address. /// /// Defined in [ZIP 316][zip-0316]. /// /// [zip-0316]: https://zips.z.cash/zip-0316 #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::hrp_unified_address` instead." )] fn hrp_unified_address(&self) -> &'static str; /// The HRP for a Bech32m-encoded mainnet Unified FVK. /// /// Defined in [ZIP 316][zip-0316]. /// /// [zip-0316]: https://zips.z.cash/zip-0316 #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::hrp_unified_fvk` instead." )] fn hrp_unified_fvk(&self) -> &'static str; /// The HRP for a Bech32m-encoded mainnet Unified IVK. /// /// Defined in [ZIP 316][zip-0316]. /// /// [zip-0316]: https://zips.z.cash/zip-0316 #[deprecated( since = "0.11.0", note = "Use `zcash_address::constants::hrp_unified_ivk` instead." )] fn hrp_unified_ivk(&self) -> &'static str; } impl private::Sealed for NetworkType {} #[allow(deprecated)] impl NetworkConstants for NetworkType { fn coin_type(&self) -> u32 { match self { NetworkType::Main => mainnet::COIN_TYPE, NetworkType::Test => testnet::COIN_TYPE, NetworkType::Regtest => regtest::COIN_TYPE, } } fn hrp_sapling_extended_spending_key(&self) -> &'static str { match self { NetworkType::Main => mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, NetworkType::Test => testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, NetworkType::Regtest => regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY, } } fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str { match self { NetworkType::Main => mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, NetworkType::Test => testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, NetworkType::Regtest => regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, } } fn hrp_sapling_payment_address(&self) -> &'static str { match self { NetworkType::Main => mainnet::HRP_SAPLING_PAYMENT_ADDRESS, NetworkType::Test => testnet::HRP_SAPLING_PAYMENT_ADDRESS, NetworkType::Regtest => regtest::HRP_SAPLING_PAYMENT_ADDRESS, } } fn b58_sprout_address_prefix(&self) -> [u8; 2] { match self { NetworkType::Main => mainnet::B58_SPROUT_ADDRESS_PREFIX, NetworkType::Test => testnet::B58_SPROUT_ADDRESS_PREFIX, NetworkType::Regtest => regtest::B58_SPROUT_ADDRESS_PREFIX, } } fn b58_pubkey_address_prefix(&self) -> [u8; 2] { match self { NetworkType::Main => mainnet::B58_PUBKEY_ADDRESS_PREFIX, NetworkType::Test => testnet::B58_PUBKEY_ADDRESS_PREFIX, NetworkType::Regtest => regtest::B58_PUBKEY_ADDRESS_PREFIX, } } fn b58_secret_key_prefix(&self) -> [u8; 1] { match self { NetworkType::Main => mainnet::B58_SECRET_KEY_PREFIX, NetworkType::Test => testnet::B58_SECRET_KEY_PREFIX, NetworkType::Regtest => regtest::B58_SECRET_KEY_PREFIX, } } fn b58_script_address_prefix(&self) -> [u8; 2] { match self { NetworkType::Main => mainnet::B58_SCRIPT_ADDRESS_PREFIX, NetworkType::Test => testnet::B58_SCRIPT_ADDRESS_PREFIX, NetworkType::Regtest => regtest::B58_SCRIPT_ADDRESS_PREFIX, } } fn hrp_tex_address(&self) -> &'static str { match self { NetworkType::Main => mainnet::HRP_TEX_ADDRESS, NetworkType::Test => testnet::HRP_TEX_ADDRESS, NetworkType::Regtest => regtest::HRP_TEX_ADDRESS, } } fn hrp_unified_address(&self) -> &'static str { match self { NetworkType::Main => mainnet::HRP_UNIFIED_ADDRESS, NetworkType::Test => testnet::HRP_UNIFIED_ADDRESS, NetworkType::Regtest => regtest::HRP_UNIFIED_ADDRESS, } } fn hrp_unified_fvk(&self) -> &'static str { match self { NetworkType::Main => mainnet::HRP_UNIFIED_FVK, NetworkType::Test => testnet::HRP_UNIFIED_FVK, NetworkType::Regtest => regtest::HRP_UNIFIED_FVK, } } fn hrp_unified_ivk(&self) -> &'static str { match self { NetworkType::Main => mainnet::HRP_UNIFIED_IVK, NetworkType::Test => testnet::HRP_UNIFIED_IVK, NetworkType::Regtest => regtest::HRP_UNIFIED_IVK, } } } /// Zcash consensus parameters. pub trait Parameters: Clone { /// Returns the type of network configured by this set of consensus parameters. fn network_type(&self) -> NetworkType; /// Returns the activation height for a particular network upgrade, /// if an activation height has been set. fn activation_height(&self, nu: NetworkUpgrade) -> Option; /// Determines whether the specified network upgrade is active as of the /// provided block height on the network to which this Parameters value applies. fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { self.activation_height(nu).is_some_and(|h| h <= height) } } impl Parameters for &P { fn network_type(&self) -> NetworkType { (*self).network_type() } fn activation_height(&self, nu: NetworkUpgrade) -> Option { (*self).activation_height(nu) } } impl private::Sealed for P {} #[allow(deprecated)] impl NetworkConstants for P { fn coin_type(&self) -> u32 { self.network_type().coin_type() } fn hrp_sapling_extended_spending_key(&self) -> &'static str { self.network_type().hrp_sapling_extended_spending_key() } fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str { self.network_type().hrp_sapling_extended_full_viewing_key() } fn hrp_sapling_payment_address(&self) -> &'static str { self.network_type().hrp_sapling_payment_address() } fn b58_sprout_address_prefix(&self) -> [u8; 2] { self.network_type().b58_sprout_address_prefix() } fn b58_pubkey_address_prefix(&self) -> [u8; 2] { self.network_type().b58_pubkey_address_prefix() } fn b58_secret_key_prefix(&self) -> [u8; 1] { self.network_type().b58_secret_key_prefix() } fn b58_script_address_prefix(&self) -> [u8; 2] { self.network_type().b58_script_address_prefix() } fn hrp_tex_address(&self) -> &'static str { self.network_type().hrp_tex_address() } fn hrp_unified_address(&self) -> &'static str { self.network_type().hrp_unified_address() } fn hrp_unified_fvk(&self) -> &'static str { self.network_type().hrp_unified_fvk() } fn hrp_unified_ivk(&self) -> &'static str { self.network_type().hrp_unified_ivk() } } /// Marker struct for the production network. #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct MainNetwork; #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(MainNetwork); /// The production network. pub const MAIN_NETWORK: MainNetwork = MainNetwork; impl Parameters for MainNetwork { fn network_type(&self) -> NetworkType { NetworkType::Main } fn activation_height(&self, nu: NetworkUpgrade) -> Option { 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)), NetworkUpgrade::Nu5 => Some(BlockHeight(1_687_104)), NetworkUpgrade::Nu6 => Some(BlockHeight(2_726_400)), NetworkUpgrade::Nu6_1 => Some(BlockHeight(3_146_400)), NetworkUpgrade::Nu6_2 => Some(BlockHeight(3_364_600)), NetworkUpgrade::Nu6_3 => Some(BlockHeight(3_428_143)), NetworkUpgrade::Nu7 => None, #[cfg(zcash_unstable = "nutachyon")] NetworkUpgrade::NuTachyon => None, } } } /// Marker struct for the test network. #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct TestNetwork; #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(TestNetwork); /// The test network. pub const TEST_NETWORK: TestNetwork = TestNetwork; impl Parameters for TestNetwork { fn network_type(&self) -> NetworkType { NetworkType::Test } fn activation_height(&self, nu: NetworkUpgrade) -> Option { 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)), NetworkUpgrade::Nu5 => Some(BlockHeight(1_842_420)), NetworkUpgrade::Nu6 => Some(BlockHeight(2_976_000)), NetworkUpgrade::Nu6_1 => Some(BlockHeight(3_536_500)), NetworkUpgrade::Nu6_2 => Some(BlockHeight(4_052_000)), NetworkUpgrade::Nu6_3 => Some(BlockHeight(4_134_000)), NetworkUpgrade::Nu7 => Some(BlockHeight(4_465_026)), #[cfg(zcash_unstable = "nutachyon")] NetworkUpgrade::NuTachyon => None, } } } /// The enumeration of known Zcash networks. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Network { /// Zcash Mainnet. MainNetwork, /// Zcash Testnet. TestNetwork, } #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(Network); impl Parameters for Network { fn network_type(&self) -> NetworkType { match self { Network::MainNetwork => NetworkType::Main, Network::TestNetwork => NetworkType::Test, } } fn activation_height(&self, nu: NetworkUpgrade) -> Option { match self { Network::MainNetwork => MAIN_NETWORK.activation_height(nu), Network::TestNetwork => TEST_NETWORK.activation_height(nu), } } } /// 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, PartialEq, Eq)] 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, /// The [Nu5] network upgrade. /// /// [Nu5]: https://z.cash/upgrade/nu5/ Nu5, /// The [Nu6] network upgrade. /// /// [Nu6]: https://z.cash/upgrade/nu6/ Nu6, /// The [Nu6.1] network upgrade. /// /// [Nu6.1]: https://z.cash/upgrade/nu6.1/ Nu6_1, /// The [Nu6.2] network upgrade. /// /// [Nu6.2]: https://z.cash/upgrade/nu6.2/ Nu6_2, /// The Ironwood / NU6.3 network upgrade. Nu6_3, /// The [Nu7 (proposed)] network upgrade. /// /// [Nu7 (proposed)]: https://z.cash/upgrade/nu7/ Nu7, /// The NuTachyon network upgrade. #[cfg(zcash_unstable = "nutachyon")] NuTachyon, } #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(NetworkUpgrade); 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"), NetworkUpgrade::Nu5 => write!(f, "Nu5"), NetworkUpgrade::Nu6 => write!(f, "Nu6"), NetworkUpgrade::Nu6_1 => write!(f, "Nu6.1"), NetworkUpgrade::Nu6_2 => write!(f, "Nu6.2"), NetworkUpgrade::Nu6_3 => write!(f, "Nu6.3"), NetworkUpgrade::Nu7 => write!(f, "Nu7"), #[cfg(zcash_unstable = "nutachyon")] NetworkUpgrade::NuTachyon => write!(f, "NuTachyon"), } } } impl NetworkUpgrade { /// Returns the consensus branch ID activated by this network upgrade. /// /// This is the inverse of [`BranchId::network_upgrade`]. pub 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, NetworkUpgrade::Nu5 => BranchId::Nu5, NetworkUpgrade::Nu6 => BranchId::Nu6, NetworkUpgrade::Nu6_1 => BranchId::Nu6_1, NetworkUpgrade::Nu6_2 => BranchId::Nu6_2, NetworkUpgrade::Nu6_3 => BranchId::Nu6_3, NetworkUpgrade::Nu7 => BranchId::Nu7, #[cfg(zcash_unstable = "nutachyon")] NetworkUpgrade::NuTachyon => BranchId::NuTachyon, } } } /// 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, NetworkUpgrade::Nu5, NetworkUpgrade::Nu6, NetworkUpgrade::Nu6_1, NetworkUpgrade::Nu6_2, NetworkUpgrade::Nu6_3, NetworkUpgrade::Nu7, #[cfg(zcash_unstable = "nutachyon")] NetworkUpgrade::NuTachyon, ]; /// The "grace period" defined in [ZIP 212]. /// /// [ZIP 212]: https://zips.z.cash/zip-0212#changes-to-the-process-of-receiving-sapling-or-orchard-notes pub const ZIP212_GRACE_PERIOD: u32 = 32256; /// The number of blocks after which a coinbase output is considered mature and spendable. /// /// From [ยง 7.1.2 of the Zcash Protocol Specification][txnconsensus]: /// > A transaction MUST NOT spend a transparent output of a coinbase transaction from a /// > block less than 100 blocks prior to the spend. /// /// [txnconsensus]: https://zips.z.cash/protocol/protocol.pdf#txnconsensus pub const COINBASE_MATURITY_BLOCKS: u32 = 100; /// 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`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/transaction/sighash/fn.signature_hash.html #[derive(Clone, Copy, Debug, PartialEq, Eq)] 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, /// The consensus rules deployed by [`NetworkUpgrade::Nu5`]. Nu5, /// The consensus rules deployed by [`NetworkUpgrade::Nu6`]. Nu6, /// The consensus rules deployed by [`NetworkUpgrade::Nu6_1`]. Nu6_1, /// The consensus rules deployed by [`NetworkUpgrade::Nu6_2`]. Nu6_2, /// The consensus rules to be deployed by [`NetworkUpgrade::Nu6_3`]. Nu6_3, /// The consensus rules to be deployed by [`NetworkUpgrade::Nu7`]. /// /// The consensus branch identifier is `0x77190AD9`. Nu7, /// The consensus rules deployed by [`NetworkUpgrade::NuTachyon`]. #[cfg(zcash_unstable = "nutachyon")] NuTachyon, } #[cfg(feature = "std")] memuse::impl_no_dynamic_usage!(BranchId); 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), 0xc2d6_d0b4 => Ok(BranchId::Nu5), 0xc8e7_1055 => Ok(BranchId::Nu6), 0x4dec_4df0 => Ok(BranchId::Nu6_1), 0x5437_f330 => Ok(BranchId::Nu6_2), 0x37a5_165b => Ok(BranchId::Nu6_3), 0x7719_0ad9 => Ok(BranchId::Nu7), #[cfg(zcash_unstable = "nutachyon")] 0xffff_fffc => Ok(BranchId::NuTachyon), _ => 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, BranchId::Nu5 => 0xc2d6_d0b4, BranchId::Nu6 => 0xc8e7_1055, BranchId::Nu6_1 => 0x4dec_4df0, BranchId::Nu6_2 => 0x5437_f330, BranchId::Nu6_3 => 0x37a5_165b, BranchId::Nu7 => 0x7719_0ad9, #[cfg(zcash_unstable = "nutachyon")] BranchId::NuTachyon => 0xffff_fffc, } } } 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 } /// Returns the network upgrade that activates this set of consensus rules, or /// `None` for the pre-Overwinter Sprout rules, which have no activation height. /// /// This is the inverse of [`NetworkUpgrade::branch_id`]. pub fn network_upgrade(&self) -> Option { Some(match self { BranchId::Sprout => return None, BranchId::Overwinter => NetworkUpgrade::Overwinter, BranchId::Sapling => NetworkUpgrade::Sapling, BranchId::Blossom => NetworkUpgrade::Blossom, BranchId::Heartwood => NetworkUpgrade::Heartwood, BranchId::Canopy => NetworkUpgrade::Canopy, BranchId::Nu5 => NetworkUpgrade::Nu5, BranchId::Nu6 => NetworkUpgrade::Nu6, BranchId::Nu6_1 => NetworkUpgrade::Nu6_1, BranchId::Nu6_2 => NetworkUpgrade::Nu6_2, BranchId::Nu6_3 => NetworkUpgrade::Nu6_3, BranchId::Nu7 => NetworkUpgrade::Nu7, #[cfg(zcash_unstable = "nutachyon")] BranchId::NuTachyon => NetworkUpgrade::NuTachyon, }) } /// Returns the range of heights for the consensus epoch associated with this branch id. /// /// The resulting tuple implements the [`RangeBounds`] trait. pub fn height_range(&self, params: &P) -> Option> { self.height_bounds(params).map(|(lower, upper)| { ( Bound::Included(lower), upper.map_or(Bound::Unbounded, Bound::Excluded), ) }) } /// Returns the range of heights for the consensus epoch associated with this branch id. /// /// The return type of this value is slightly more precise than [`Self::height_range`]: /// - `Some((x, Some(y)))` means that the consensus rules corresponding to this branch id /// are in effect for the range `x..y` /// - `Some((x, None))` means that the consensus rules corresponding to this branch id are /// in effect for the range `x..` /// - `None` means that the consensus rules corresponding to this branch id are never in effect. pub fn height_bounds( &self, params: &P, ) -> Option<(BlockHeight, Option)> { let mut later = UPGRADES_IN_ORDER.iter().copied(); let lower = if *self == BranchId::Sprout { BlockHeight(0) } else { let upgrade = later.find(|upgrade| upgrade.branch_id() == *self)?; params.activation_height(upgrade)? }; let upper = later .filter_map(|upgrade| params.activation_height(upgrade)) .min(); if upper.is_some_and(|upper| upper <= lower) { None } else { Some((lower, upper)) } } pub fn sprout_uses_groth_proofs(&self) -> bool { !matches!(self, BranchId::Sprout | BranchId::Overwinter) } // Returns `true` for consensus branches that support the Sprout protocol, `false` otherwise.. pub fn has_sprout(&self) -> bool { use BranchId::*; match self { Sprout | Overwinter | Sapling | Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu6_2 => true, BranchId::Nu6_3 => true, BranchId::Nu7 => false, #[cfg(zcash_unstable = "nutachyon")] BranchId::NuTachyon => false, } } // Returns `true` for consensus branches that support the Sapling protocol, `false` otherwise.. pub fn has_sapling(&self) -> bool { use BranchId::*; match self { Sprout | Overwinter => false, Sapling | Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu6_2 => true, BranchId::Nu6_3 => true, BranchId::Nu7 => true, #[cfg(zcash_unstable = "nutachyon")] BranchId::NuTachyon => true, } } // Returns `true` for consensus branches that support the Orchard protocol, `false` otherwise. pub fn has_orchard(&self) -> bool { use BranchId::*; match self { Sprout | Overwinter | Sapling | Blossom | Heartwood | Canopy => false, Nu5 | Nu6 | Nu6_1 | Nu6_2 => true, BranchId::Nu6_3 => true, BranchId::Nu7 => true, #[cfg(zcash_unstable = "nutachyon")] BranchId::NuTachyon => true, } } /// Returns the revision of the Orchard protocol in effect under this consensus /// branch, or `None` for branches that predate NU5 (under which the Orchard /// protocol is not supported). pub fn orchard_protocol_revision(&self) -> Option { use BranchId::*; match self { Sprout | Overwinter | Sapling | Blossom | Heartwood | Canopy => None, Nu5 | Nu6 | Nu6_1 => Some(OrchardProtocolRevision::InsecureV1), Nu6_2 => Some(OrchardProtocolRevision::V2), Nu6_3 => Some(OrchardProtocolRevision::V3), Nu7 => Some(OrchardProtocolRevision::V3), #[cfg(zcash_unstable = "nutachyon")] NuTachyon => Some(OrchardProtocolRevision::V3), } } } /// The revision of the Orchard protocol deployed by a network upgrade. /// /// The revisions correspond one-to-one to the `orchard` crate's `ProtocolVersion`; /// this type exists so that crates that do not depend on `orchard` can express which /// protocol revision a consensus branch selects. Use /// [`BranchId::orchard_protocol_revision`] to obtain the revision in effect under a /// given consensus branch. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum OrchardProtocolRevision { /// The original revision of the Orchard protocol, deployed at NU5 and used prior /// to NU6.2. Uses the historical unsound Orchard circuit; cross-address transfers /// are permitted. InsecureV1, /// The revision of the Orchard protocol deployed at NU6.2. Uses the post-NU6.2 /// fixed circuit; cross-address transfers are permitted. V2, /// The revision of the Orchard protocol deployed at NU6.3, which introduces the /// Ironwood value pool. Uses the post-NU6.3 circuit; cross-address transfers are /// prohibited for the Orchard value pool and permitted for the Ironwood value /// pool. V3, } #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::sample::select; use proptest::strategy::{Just, Strategy}; use super::{BlockHeight, BranchId, Parameters}; pub fn arb_branch_id() -> impl Strategy { select(vec![ BranchId::Sprout, BranchId::Overwinter, BranchId::Sapling, BranchId::Blossom, BranchId::Heartwood, BranchId::Canopy, BranchId::Nu5, BranchId::Nu6, BranchId::Nu6_1, BranchId::Nu6_2, BranchId::Nu6_3, BranchId::Nu7, #[cfg(zcash_unstable = "nutachyon")] BranchId::NuTachyon, ]) } /// An arbitrary [`BlockHeight`], within the range heights realistically take. pub fn arb_block_height() -> impl Strategy { (0u32..5_000_000).prop_map(BlockHeight::from_u32) } /// An arbitrary [`BlockHeight`], within the range of heights for the given consensus branch on /// the specified network. pub fn arb_height_for_branch( branch_id: BranchId, params: &P, ) -> impl Strategy> { branch_id .height_bounds(params) .map_or(Strategy::boxed(Just(None)), |(lower, upper)| { Strategy::boxed( (lower.0..upper.map_or(u32::MAX, |u| u.0)).prop_map(|h| Some(BlockHeight(h))), ) }) } /// An arbitrary [`BlockHeight`], within the range of heights for the given consensus branch on /// the specified network. #[deprecated(note = "Use arb_height_for_branch instead.")] pub fn arb_height( branch_id: BranchId, params: &P, ) -> impl Strategy> { arb_height_for_branch(branch_id, params) } #[cfg(feature = "test-dependencies")] impl incrementalmerkletree_testing::TestCheckpoint for BlockHeight { fn from_u64(value: u64) -> Self { BlockHeight(u32::try_from(value).expect("Test checkpoint ids do not exceed 32 bits")) } } } #[cfg(test)] mod tests { use super::{ BlockHeight, BranchId, MAIN_NETWORK, NetworkUpgrade, Parameters, TEST_NETWORK, UPGRADES_IN_ORDER, }; #[test] #[cfg(feature = "local-consensus")] fn local_epoch_bounds_match_branch_selection() { use crate::local_consensus::LocalNetwork; for nu6_3 in [None, Some(BlockHeight(10)), Some(BlockHeight(30))] { let network = LocalNetwork { overwinter: None, sapling: None, blossom: None, heartwood: None, canopy: None, nu5: None, nu6: None, nu6_1: None, nu6_2: Some(BlockHeight(5)), nu6_3, nu7: Some(BlockHeight(20)), #[cfg(zcash_unstable = "nutachyon")] nu_tachyon: Some(BlockHeight(15)), }; for branch in core::iter::once(BranchId::Sprout) .chain(UPGRADES_IN_ORDER.iter().map(|upgrade| upgrade.branch_id())) { for height in (0..40).map(BlockHeight) { let contains = branch .height_bounds(&network) .is_some_and(|(lower, upper)| { height >= lower && upper.is_none_or(|upper| height < upper) }); assert_eq!( contains, BranchId::for_height(&network, height) == branch, "branch {branch:?}, height {height:?}, network {network:?}" ); } } } } #[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 ( MAIN_NETWORK.activation_height(nu_a), MAIN_NETWORK.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!(!MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(0))); assert!(!MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_499))); assert!(MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_500))); } #[test] fn branch_id_from_u32() { assert_eq!(BranchId::try_from(0), Ok(BranchId::Sprout)); #[cfg(zcash_unstable = "nutachyon")] { assert_eq!(BranchId::try_from(0xffff_fffc), Ok(BranchId::NuTachyon)); assert_eq!(u32::from(BranchId::NuTachyon), 0xffff_fffc); } assert!(BranchId::try_from(1).is_err()); } #[test] fn nu7_branch_id_round_trip_and_retired_ids() { /// The NU7 consensus branch identifier. const NU7_BRANCH_ID: u32 = 0x7719_0ad9; /// The identifier assigned to the earlier NU7 proposal. const RETIRED_NU7_BRANCH_ID: u32 = 0x7719_0ad8; /// The placeholder used before NU7 was assigned an identifier. const NU7_PLACEHOLDER: u32 = 0xffff_ffff; assert_eq!(BranchId::try_from(NU7_BRANCH_ID), Ok(BranchId::Nu7)); assert_eq!(u32::from(BranchId::Nu7), NU7_BRANCH_ID); assert_eq!( BranchId::try_from(u32::from(BranchId::Nu7)), Ok(BranchId::Nu7), ); assert!(BranchId::try_from(RETIRED_NU7_BRANCH_ID).is_err()); assert!(BranchId::try_from(NU7_PLACEHOLDER).is_err()); } #[test] fn orchard_protocol_revision() { use super::OrchardProtocolRevision; assert_eq!(BranchId::Canopy.orchard_protocol_revision(), None); assert_eq!( BranchId::Nu5.orchard_protocol_revision(), Some(OrchardProtocolRevision::InsecureV1) ); assert_eq!( BranchId::Nu6_1.orchard_protocol_revision(), Some(OrchardProtocolRevision::InsecureV1) ); assert_eq!( BranchId::Nu6_2.orchard_protocol_revision(), Some(OrchardProtocolRevision::V2) ); assert_eq!( BranchId::Nu6_3.orchard_protocol_revision(), Some(OrchardProtocolRevision::V3) ); assert_eq!( BranchId::Nu7.orchard_protocol_revision(), Some(OrchardProtocolRevision::V3) ); #[cfg(zcash_unstable = "nutachyon")] assert_eq!( BranchId::NuTachyon.orchard_protocol_revision(), Some(OrchardProtocolRevision::V3) ); } #[test] fn branch_id_for_height() { assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(0)), BranchId::Sprout, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(419_199)), BranchId::Overwinter, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(419_200)), BranchId::Sapling, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(903_000)), BranchId::Heartwood, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(1_046_400)), BranchId::Canopy, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(1_687_104)), BranchId::Nu5, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(2_726_399)), BranchId::Nu5, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(2_726_400)), BranchId::Nu6, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(3_146_400)), BranchId::Nu6_1, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(3_364_599)), BranchId::Nu6_1, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(3_364_600)), BranchId::Nu6_2, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(3_428_142)), BranchId::Nu6_2, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(3_428_143)), BranchId::Nu6_3, ); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(5_000_000)), BranchId::Nu6_3, ); } #[test] fn nu7_activates_on_testnet_only() { /// The height at which NU7 activates on Testnet. const TESTNET_NU7_ACTIVATION: BlockHeight = BlockHeight(4_465_026); assert_eq!(MAIN_NETWORK.activation_height(NetworkUpgrade::Nu7), None); assert_eq!( BranchId::for_height(&MAIN_NETWORK, BlockHeight(u32::MAX)), BranchId::Nu6_3, ); assert_eq!( TEST_NETWORK.activation_height(NetworkUpgrade::Nu7), Some(TESTNET_NU7_ACTIVATION), ); assert_eq!( BranchId::for_height(&TEST_NETWORK, TESTNET_NU7_ACTIVATION - 1), BranchId::Nu6_3, ); assert_eq!( BranchId::for_height(&TEST_NETWORK, TESTNET_NU7_ACTIVATION), BranchId::Nu7, ); } }