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