use NetworkUpgrade::*;
use crate::block;
use crate::parameters::{Network, Network::*};
use crate::serialization::BytesInDisplayOrder;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use chrono::{DateTime, Duration, Utc};
use hex::{FromHex, ToHex};
use strum::{EnumIter, IntoEnumIterator};
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
#[derive(
Copy, Clone, EnumIter, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd,
)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub enum NetworkUpgrade {
Genesis,
BeforeOverwinter,
Overwinter,
Sapling,
Blossom,
Heartwood,
Canopy,
#[serde(rename = "NU5")]
Nu5,
#[serde(rename = "NU6")]
Nu6,
#[serde(rename = "NU6.1")]
Nu6_1,
#[serde(rename = "NU7")]
Nu7,
#[cfg(zcash_unstable = "zfuture")]
ZFuture,
}
impl TryFrom<u32> for NetworkUpgrade {
type Error = crate::Error;
fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
CONSENSUS_BRANCH_IDS
.iter()
.find(|id| id.1 == ConsensusBranchId(branch_id))
.map(|nu| nu.0)
.ok_or(Self::Error::InvalidConsensusBranchId)
}
}
impl fmt::Display for NetworkUpgrade {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[allow(unused)]
pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
use super::constants::activation_heights::mainnet::*;
&[
(block::Height(0), Genesis),
(BEFORE_OVERWINTER, BeforeOverwinter),
(OVERWINTER, Overwinter),
(SAPLING, Sapling),
(BLOSSOM, Blossom),
(HEARTWOOD, Heartwood),
(CANOPY, Canopy),
(NU5, Nu5),
(NU6, Nu6),
(NU6_1, Nu6_1),
]
};
#[allow(unused)]
pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
use super::constants::activation_heights::testnet::*;
&[
(block::Height(0), Genesis),
(BEFORE_OVERWINTER, BeforeOverwinter),
(OVERWINTER, Overwinter),
(SAPLING, Sapling),
(BLOSSOM, Blossom),
(HEARTWOOD, Heartwood),
(CANOPY, Canopy),
(NU5, Nu5),
(NU6, Nu6),
(NU6_1, Nu6_1),
]
};
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ConsensusBranchId(pub(crate) u32);
impl BytesInDisplayOrder<false, 4> for ConsensusBranchId {
fn bytes_in_serialized_order(&self) -> [u8; 4] {
self.0.to_be_bytes()
}
fn from_bytes_in_serialized_order(bytes: [u8; 4]) -> Self {
ConsensusBranchId(u32::from_be_bytes(bytes))
}
}
impl From<ConsensusBranchId> for u32 {
fn from(branch: ConsensusBranchId) -> u32 {
branch.0
}
}
impl From<u32> for ConsensusBranchId {
fn from(branch: u32) -> Self {
ConsensusBranchId(branch)
}
}
impl ToHex for &ConsensusBranchId {
fn encode_hex<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex()
}
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex_upper()
}
}
impl ToHex for ConsensusBranchId {
fn encode_hex<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex()
}
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex_upper()
}
}
impl FromHex for ConsensusBranchId {
type Error = <[u8; 4] as FromHex>::Error;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let branch = <[u8; 4]>::from_hex(hex)?;
Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
}
}
impl fmt::Display for ConsensusBranchId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.encode_hex::<String>())
}
}
impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
type Error = crate::Error;
fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
zcash_primitives::consensus::BranchId::try_from(u32::from(id))
.map_err(|_| Self::Error::InvalidConsensusBranchId)
}
}
pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
(Overwinter, ConsensusBranchId(0x5ba81b19)),
(Sapling, ConsensusBranchId(0x76b809bb)),
(Blossom, ConsensusBranchId(0x2bb40e60)),
(Heartwood, ConsensusBranchId(0xf5b9230b)),
(Canopy, ConsensusBranchId(0xe9ff75a6)),
(Nu5, ConsensusBranchId(0xc2d6d0b4)),
(Nu6, ConsensusBranchId(0xc8e71055)),
(Nu6_1, ConsensusBranchId(0x4dec4df0)),
#[cfg(any(test, feature = "zebra-test"))]
(Nu7, ConsensusBranchId(0xffffffff)),
#[cfg(zcash_unstable = "zfuture")]
(ZFuture, ConsensusBranchId(0xffffffff)),
];
const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
pub const POW_AVERAGING_WINDOW: usize = 17;
const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
impl Network {
pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
match self {
Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
Testnet(params) => params.activation_heights().clone(),
}
}
pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
NetworkUpgrade::iter()
.filter_map(|nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
.collect()
}
}
impl NetworkUpgrade {
pub fn current_with_activation_height(
network: &Network,
height: block::Height,
) -> (NetworkUpgrade, block::Height) {
network
.activation_list()
.range(..=height)
.map(|(&h, &nu)| (nu, h))
.next_back()
.expect("every height has a current network upgrade")
}
pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
network
.activation_list()
.range(..=height)
.map(|(_, nu)| *nu)
.next_back()
.expect("every height has a current network upgrade")
}
pub fn next_upgrade(self) -> Option<Self> {
Self::iter().skip_while(|&nu| self != nu).nth(1)
}
pub fn previous_upgrade(self) -> Option<Self> {
Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
}
#[cfg(test)]
pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
use std::ops::Bound::*;
network
.activation_list()
.range((Excluded(height), Unbounded))
.map(|(_, nu)| *nu)
.next()
}
pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
network
.activation_list()
.iter()
.find(|(_, nu)| nu == &self)
.map(|(height, _)| *height)
.or_else(|| {
self.next_upgrade()
.and_then(|next_nu| next_nu.activation_height(network))
})
}
pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
network.activation_list().contains_key(&height)
}
pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
CONSENSUS_BRANCH_IDS.iter().cloned().collect()
}
pub fn branch_id(&self) -> Option<ConsensusBranchId> {
NetworkUpgrade::branch_id_list().get(self).cloned()
}
pub fn target_spacing(&self) -> Duration {
let spacing_seconds = match self {
Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu7 => {
POST_BLOSSOM_POW_TARGET_SPACING.into()
}
#[cfg(zcash_unstable = "zfuture")]
ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
};
Duration::seconds(spacing_seconds)
}
pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
NetworkUpgrade::current(network, height).target_spacing()
}
pub fn target_spacings(
network: &Network,
) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
[
(NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
(
NetworkUpgrade::Blossom,
POST_BLOSSOM_POW_TARGET_SPACING.into(),
),
]
.into_iter()
.filter_map(move |(upgrade, spacing_seconds)| {
let activation_height = upgrade.activation_height(network)?;
let target_spacing = Duration::seconds(spacing_seconds);
Some((activation_height, target_spacing))
})
}
pub fn minimum_difficulty_spacing_for_height(
network: &Network,
height: block::Height,
) -> Option<Duration> {
match (network, height) {
(Network::Testnet(_params), height)
if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
{
None
}
(Network::Mainnet, _) => None,
(Network::Testnet(_params), _) => {
let network_upgrade = NetworkUpgrade::current(network, height);
Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
}
}
}
pub fn is_testnet_min_difficulty_block(
network: &Network,
block_height: block::Height,
block_time: DateTime<Utc>,
previous_block_time: DateTime<Utc>,
) -> bool {
let block_time_gap = block_time - previous_block_time;
if let Some(min_difficulty_gap) =
NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
{
block_time_gap > min_difficulty_gap
} else {
false
}
}
pub fn averaging_window_timespan(&self) -> Duration {
self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
}
pub fn averaging_window_timespan_for_height(
network: &Network,
height: block::Height,
) -> Duration {
NetworkUpgrade::current(network, height).averaging_window_timespan()
}
pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
<Self as IntoEnumIterator>::iter()
}
}
impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
match nu {
zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
#[cfg(zcash_unstable = "nu7")]
zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
#[cfg(zcash_unstable = "zfuture")]
zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
}
}
}
impl ConsensusBranchId {
pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
NetworkUpgrade::current(network, height).branch_id()
}
}