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