Skip to main content

zebra_chain/parameters/
network_upgrade.rs

1//! Network upgrade consensus parameters for Zcash.
2
3use NetworkUpgrade::*;
4
5use crate::block;
6use crate::parameters::{Network, Network::*};
7use crate::serialization::BytesInDisplayOrder;
8
9use std::collections::{BTreeMap, HashMap};
10use std::fmt;
11
12use chrono::{DateTime, Duration, Utc};
13use hex::{FromHex, ToHex};
14
15use strum::{EnumIter, IntoEnumIterator};
16
17#[cfg(any(test, feature = "proptest-impl"))]
18use proptest_derive::Arbitrary;
19
20/// A Zcash network upgrade.
21///
22/// Network upgrades change the Zcash network protocol or consensus rules. Note that they have no
23/// designated codenames from NU5 onwards.
24///
25/// Enum variants must be ordered by activation height.
26#[derive(
27    Copy, Clone, EnumIter, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd,
28)]
29#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
30pub enum NetworkUpgrade {
31    /// The Zcash protocol for a Genesis block.
32    ///
33    /// Zcash genesis blocks use a different set of consensus rules from
34    /// other BeforeOverwinter blocks, so we treat them like a separate network
35    /// upgrade.
36    Genesis,
37    /// The Zcash protocol before the Overwinter upgrade.
38    ///
39    /// We avoid using `Sprout`, because the specification says that Sprout
40    /// is the name of the pre-Sapling protocol, before and after Overwinter.
41    BeforeOverwinter,
42    /// The Zcash protocol after the Overwinter upgrade.
43    Overwinter,
44    /// The Zcash protocol after the Sapling upgrade.
45    Sapling,
46    /// The Zcash protocol after the Blossom upgrade.
47    Blossom,
48    /// The Zcash protocol after the Heartwood upgrade.
49    Heartwood,
50    /// The Zcash protocol after the Canopy upgrade.
51    Canopy,
52    /// The Zcash protocol after the NU5 upgrade.
53    #[serde(rename = "NU5")]
54    Nu5,
55    /// The Zcash protocol after the NU6 upgrade.
56    #[serde(rename = "NU6")]
57    Nu6,
58    /// The Zcash protocol after the NU6.1 upgrade.
59    #[serde(rename = "NU6.1")]
60    Nu6_1,
61    /// The Zcash protocol after the NU7 upgrade.
62    #[serde(rename = "NU7")]
63    Nu7,
64
65    #[cfg(zcash_unstable = "zfuture")]
66    ZFuture,
67}
68
69impl TryFrom<u32> for NetworkUpgrade {
70    type Error = crate::Error;
71
72    fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
73        CONSENSUS_BRANCH_IDS
74            .iter()
75            .find(|id| id.1 == ConsensusBranchId(branch_id))
76            .map(|nu| nu.0)
77            .ok_or(Self::Error::InvalidConsensusBranchId)
78    }
79}
80
81impl fmt::Display for NetworkUpgrade {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        // Same as the debug representation for now
84        fmt::Debug::fmt(self, f)
85    }
86}
87
88/// Mainnet network upgrade activation heights.
89///
90/// This is actually a bijective map, but it is const, so we use a vector, and
91/// do the uniqueness check in the unit tests.
92///
93/// # Correctness
94///
95/// Don't use this directly; use NetworkUpgrade::activation_list() so that
96/// we can switch to fake activation heights for some tests.
97#[allow(unused)]
98pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
99    use super::constants::activation_heights::mainnet::*;
100    &[
101        (block::Height(0), Genesis),
102        (BEFORE_OVERWINTER, BeforeOverwinter),
103        (OVERWINTER, Overwinter),
104        (SAPLING, Sapling),
105        (BLOSSOM, Blossom),
106        (HEARTWOOD, Heartwood),
107        (CANOPY, Canopy),
108        (NU5, Nu5),
109        (NU6, Nu6),
110        (NU6_1, Nu6_1),
111    ]
112};
113/// Testnet network upgrade activation heights.
114///
115/// This is actually a bijective map, but it is const, so we use a vector, and
116/// do the uniqueness check in the unit tests.
117///
118/// # Correctness
119///
120/// Don't use this directly; use NetworkUpgrade::activation_list() so that
121/// we can switch to fake activation heights for some tests.
122#[allow(unused)]
123pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
124    use super::constants::activation_heights::testnet::*;
125    &[
126        (block::Height(0), Genesis),
127        (BEFORE_OVERWINTER, BeforeOverwinter),
128        (OVERWINTER, Overwinter),
129        (SAPLING, Sapling),
130        (BLOSSOM, Blossom),
131        (HEARTWOOD, Heartwood),
132        (CANOPY, Canopy),
133        (NU5, Nu5),
134        (NU6, Nu6),
135        (NU6_1, Nu6_1),
136    ]
137};
138
139/// The Consensus Branch Id, used to bind transactions and blocks to a
140/// particular network upgrade.
141#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
142pub struct ConsensusBranchId(pub(crate) u32);
143
144impl BytesInDisplayOrder<false, 4> for ConsensusBranchId {
145    fn bytes_in_serialized_order(&self) -> [u8; 4] {
146        self.0.to_be_bytes()
147    }
148
149    fn from_bytes_in_serialized_order(bytes: [u8; 4]) -> Self {
150        ConsensusBranchId(u32::from_be_bytes(bytes))
151    }
152}
153
154impl From<ConsensusBranchId> for u32 {
155    fn from(branch: ConsensusBranchId) -> u32 {
156        branch.0
157    }
158}
159
160impl From<u32> for ConsensusBranchId {
161    fn from(branch: u32) -> Self {
162        ConsensusBranchId(branch)
163    }
164}
165
166impl ToHex for &ConsensusBranchId {
167    fn encode_hex<T: FromIterator<char>>(&self) -> T {
168        self.bytes_in_display_order().encode_hex()
169    }
170
171    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
172        self.bytes_in_display_order().encode_hex_upper()
173    }
174}
175
176impl ToHex for ConsensusBranchId {
177    fn encode_hex<T: FromIterator<char>>(&self) -> T {
178        self.bytes_in_display_order().encode_hex()
179    }
180
181    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
182        self.bytes_in_display_order().encode_hex_upper()
183    }
184}
185
186impl FromHex for ConsensusBranchId {
187    type Error = <[u8; 4] as FromHex>::Error;
188
189    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
190        let branch = <[u8; 4]>::from_hex(hex)?;
191        Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
192    }
193}
194
195impl fmt::Display for ConsensusBranchId {
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197        f.write_str(&self.encode_hex::<String>())
198    }
199}
200
201impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
202    type Error = crate::Error;
203
204    fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
205        zcash_primitives::consensus::BranchId::try_from(u32::from(id))
206            .map_err(|_| Self::Error::InvalidConsensusBranchId)
207    }
208}
209
210/// Network Upgrade Consensus Branch Ids.
211///
212/// Branch ids are the same for mainnet and testnet. If there is a testnet
213/// rollback after a bug, the branch id changes.
214///
215/// Branch ids were introduced in the Overwinter upgrade, so there are no
216/// Genesis or BeforeOverwinter branch ids.
217///
218/// This is actually a bijective map, but it is const, so we use a vector, and
219/// do the uniqueness check in the unit tests.
220pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
221    (Overwinter, ConsensusBranchId(0x5ba81b19)),
222    (Sapling, ConsensusBranchId(0x76b809bb)),
223    (Blossom, ConsensusBranchId(0x2bb40e60)),
224    (Heartwood, ConsensusBranchId(0xf5b9230b)),
225    (Canopy, ConsensusBranchId(0xe9ff75a6)),
226    (Nu5, ConsensusBranchId(0xc2d6d0b4)),
227    (Nu6, ConsensusBranchId(0xc8e71055)),
228    (Nu6_1, ConsensusBranchId(0x4dec4df0)),
229    // TODO: set below to (Nu7, ConsensusBranchId(0x77190ad8)), once the same value is set in librustzcash
230    #[cfg(any(test, feature = "zebra-test"))]
231    (Nu7, ConsensusBranchId(0xffffffff)),
232    #[cfg(zcash_unstable = "zfuture")]
233    (ZFuture, ConsensusBranchId(0xffffffff)),
234];
235
236/// The target block spacing before Blossom.
237const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
238
239/// The target block spacing after Blossom activation.
240pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
241
242/// The averaging window for difficulty threshold arithmetic mean calculations.
243///
244/// `PoWAveragingWindow` in the Zcash specification.
245pub const POW_AVERAGING_WINDOW: usize = 17;
246
247/// The multiplier used to derive the testnet minimum difficulty block time gap
248/// threshold.
249///
250/// Based on <https://zips.z.cash/zip-0208#minimum-difficulty-blocks-on-the-test-network>
251const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
252
253/// The start height for the testnet minimum difficulty consensus rule.
254///
255/// Based on <https://zips.z.cash/zip-0208#minimum-difficulty-blocks-on-the-test-network>
256const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
257
258/// The activation height for the block maximum time rule on Testnet.
259///
260/// Part of the block header consensus rules in the Zcash specification at
261/// <https://zips.z.cash/protocol/protocol.pdf#blockheader>
262pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
263
264impl Network {
265    /// Returns a map between activation heights and network upgrades for `network`,
266    /// in ascending height order.
267    ///
268    /// If the activation height of a future upgrade is not known, that
269    /// network upgrade does not appear in the list.
270    ///
271    /// This is actually a bijective map.
272    ///
273    /// Note: This skips implicit network upgrade activations, use [`Network::full_activation_list`]
274    ///       to get an explicit list of all network upgrade activations.
275    pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
276        match self {
277            Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
278            Testnet(params) => params.activation_heights().clone(),
279        }
280    }
281
282    /// Returns a vector of all implicit and explicit network upgrades for `network`,
283    /// in ascending height order.
284    pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
285        NetworkUpgrade::iter()
286            .filter_map(|nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
287            .collect()
288    }
289}
290
291impl NetworkUpgrade {
292    /// Returns the current network upgrade and its activation height for `network` and `height`.
293    pub fn current_with_activation_height(
294        network: &Network,
295        height: block::Height,
296    ) -> (NetworkUpgrade, block::Height) {
297        network
298            .activation_list()
299            .range(..=height)
300            .map(|(&h, &nu)| (nu, h))
301            .next_back()
302            .expect("every height has a current network upgrade")
303    }
304
305    /// Returns the current network upgrade for `network` and `height`.
306    pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
307        network
308            .activation_list()
309            .range(..=height)
310            .map(|(_, nu)| *nu)
311            .next_back()
312            .expect("every height has a current network upgrade")
313    }
314
315    /// Returns the next expected network upgrade after this network upgrade.
316    pub fn next_upgrade(self) -> Option<Self> {
317        Self::iter().skip_while(|&nu| self != nu).nth(1)
318    }
319
320    /// Returns the previous network upgrade before this network upgrade.
321    pub fn previous_upgrade(self) -> Option<Self> {
322        Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
323    }
324
325    /// Returns the next network upgrade for `network` and `height`.
326    ///
327    /// Returns None if the next upgrade has not been implemented in Zebra
328    /// yet.
329    #[cfg(test)]
330    pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
331        use std::ops::Bound::*;
332
333        network
334            .activation_list()
335            .range((Excluded(height), Unbounded))
336            .map(|(_, nu)| *nu)
337            .next()
338    }
339
340    /// Returns the activation height for this network upgrade on `network`, or
341    ///
342    /// Returns the activation height of the first network upgrade that follows
343    /// this network upgrade if there is no activation height for this network upgrade
344    /// such as on Regtest or a configured Testnet where multiple network upgrades have the
345    /// same activation height, or if one is omitted when others that follow it are included.
346    ///
347    /// Returns None if this network upgrade is a future upgrade, and its
348    /// activation height has not been set yet.
349    ///
350    /// Returns None if this network upgrade has not been configured on a Testnet or Regtest.
351    pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
352        network
353            .activation_list()
354            .iter()
355            .find(|(_, nu)| nu == &self)
356            .map(|(height, _)| *height)
357            .or_else(|| {
358                self.next_upgrade()
359                    .and_then(|next_nu| next_nu.activation_height(network))
360            })
361    }
362
363    /// Returns `true` if `height` is the activation height of any network upgrade
364    /// on `network`.
365    ///
366    /// Use [`NetworkUpgrade::activation_height`] to get the specific network
367    /// upgrade.
368    pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
369        network.activation_list().contains_key(&height)
370    }
371
372    /// Returns an unordered mapping between NetworkUpgrades and their ConsensusBranchIds.
373    ///
374    /// Branch ids are the same for mainnet and testnet.
375    ///
376    /// If network upgrade does not have a branch id, that network upgrade does
377    /// not appear in the list.
378    ///
379    /// This is actually a bijective map.
380    pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
381        CONSENSUS_BRANCH_IDS.iter().cloned().collect()
382    }
383
384    /// Returns the consensus branch id for this network upgrade.
385    ///
386    /// Returns None if this network upgrade has no consensus branch id.
387    pub fn branch_id(&self) -> Option<ConsensusBranchId> {
388        NetworkUpgrade::branch_id_list().get(self).cloned()
389    }
390
391    /// Returns the target block spacing for the network upgrade.
392    ///
393    /// Based on [`PRE_BLOSSOM_POW_TARGET_SPACING`] and
394    /// [`POST_BLOSSOM_POW_TARGET_SPACING`] from the Zcash specification.
395    pub fn target_spacing(&self) -> Duration {
396        let spacing_seconds = match self {
397            Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
398            Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu7 => {
399                POST_BLOSSOM_POW_TARGET_SPACING.into()
400            }
401
402            #[cfg(zcash_unstable = "zfuture")]
403            ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
404        };
405
406        Duration::seconds(spacing_seconds)
407    }
408
409    /// Returns the target block spacing for `network` and `height`.
410    ///
411    /// See [`NetworkUpgrade::target_spacing`] for details.
412    pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
413        NetworkUpgrade::current(network, height).target_spacing()
414    }
415
416    /// Returns all the target block spacings for `network` and the heights where they start.
417    pub fn target_spacings(
418        network: &Network,
419    ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
420        [
421            (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
422            (
423                NetworkUpgrade::Blossom,
424                POST_BLOSSOM_POW_TARGET_SPACING.into(),
425            ),
426        ]
427        .into_iter()
428        .filter_map(move |(upgrade, spacing_seconds)| {
429            let activation_height = upgrade.activation_height(network)?;
430            let target_spacing = Duration::seconds(spacing_seconds);
431            Some((activation_height, target_spacing))
432        })
433    }
434
435    /// Returns the minimum difficulty block spacing for `network` and `height`.
436    /// Returns `None` if the testnet minimum difficulty consensus rule is not active.
437    ///
438    /// Based on <https://zips.z.cash/zip-0208#minimum-difficulty-blocks-on-the-test-network>
439    pub fn minimum_difficulty_spacing_for_height(
440        network: &Network,
441        height: block::Height,
442    ) -> Option<Duration> {
443        match (network, height) {
444            // TODO: Move `TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT` to a field on testnet::Parameters (#8364)
445            (Network::Testnet(_params), height)
446                if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
447            {
448                None
449            }
450            (Network::Mainnet, _) => None,
451            (Network::Testnet(_params), _) => {
452                let network_upgrade = NetworkUpgrade::current(network, height);
453                Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
454            }
455        }
456    }
457
458    /// Returns true if the gap between `block_time` and `previous_block_time` is
459    /// greater than the Testnet minimum difficulty time gap. This time gap
460    /// depends on the `network` and `block_height`.
461    ///
462    /// Returns false on Mainnet, when `block_height` is less than the minimum
463    /// difficulty start height, and when the time gap is too small.
464    ///
465    /// `block_time` can be less than, equal to, or greater than
466    /// `previous_block_time`, because block times are provided by miners.
467    ///
468    /// Implements the Testnet minimum difficulty adjustment from ZIPs 205 and 208.
469    ///
470    /// Spec Note: Some parts of ZIPs 205 and 208 previously specified an incorrect
471    /// check for the time gap. This function implements the correct "greater than"
472    /// check.
473    pub fn is_testnet_min_difficulty_block(
474        network: &Network,
475        block_height: block::Height,
476        block_time: DateTime<Utc>,
477        previous_block_time: DateTime<Utc>,
478    ) -> bool {
479        let block_time_gap = block_time - previous_block_time;
480        if let Some(min_difficulty_gap) =
481            NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
482        {
483            block_time_gap > min_difficulty_gap
484        } else {
485            false
486        }
487    }
488
489    /// Returns the averaging window timespan for the network upgrade.
490    ///
491    /// `AveragingWindowTimespan` from the Zcash specification.
492    pub fn averaging_window_timespan(&self) -> Duration {
493        self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
494    }
495
496    /// Returns the averaging window timespan for `network` and `height`.
497    ///
498    /// See [`NetworkUpgrade::averaging_window_timespan`] for details.
499    pub fn averaging_window_timespan_for_height(
500        network: &Network,
501        height: block::Height,
502    ) -> Duration {
503        NetworkUpgrade::current(network, height).averaging_window_timespan()
504    }
505
506    /// Returns an iterator over [`NetworkUpgrade`] variants.
507    pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
508        <Self as IntoEnumIterator>::iter()
509    }
510}
511
512impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
513    fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
514        match nu {
515            zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
516            zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
517            zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
518            zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
519            zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
520            zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
521            zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
522            zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
523            #[cfg(zcash_unstable = "nu7")]
524            zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
525            #[cfg(zcash_unstable = "zfuture")]
526            zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
527        }
528    }
529}
530
531impl ConsensusBranchId {
532    /// The value used by `zcashd` RPCs for missing consensus branch IDs.
533    ///
534    /// # Consensus
535    ///
536    /// This value must only be used in RPCs.
537    ///
538    /// The consensus rules handle missing branch IDs by rejecting blocks and transactions,
539    /// so this substitute value must not be used in consensus-critical code.
540    pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
541
542    /// Returns the current consensus branch id for `network` and `height`.
543    ///
544    /// Returns None if the network has no branch id at this height.
545    pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
546        NetworkUpgrade::current(network, height).branch_id()
547    }
548}