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