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