ironfish_primitives/
consensus.rs

1//! Consensus logic and parameters.
2
3use std::cmp::{Ord, Ordering};
4use std::convert::TryFrom;
5use std::fmt;
6use std::ops::{Add, Bound, RangeBounds, Sub};
7use zcash_address;
8
9use crate::constants;
10
11/// A wrapper type representing blockchain heights. Safe conversion from
12/// various integer types, as well as addition and subtraction, are provided.
13#[repr(transparent)]
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct BlockHeight(u32);
16
17pub const H0: BlockHeight = BlockHeight(0);
18
19impl BlockHeight {
20    pub const fn from_u32(v: u32) -> BlockHeight {
21        BlockHeight(v)
22    }
23}
24
25impl fmt::Display for BlockHeight {
26    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27        self.0.fmt(formatter)
28    }
29}
30
31impl Ord for BlockHeight {
32    fn cmp(&self, other: &Self) -> Ordering {
33        self.0.cmp(&other.0)
34    }
35}
36
37impl PartialOrd for BlockHeight {
38    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
39        Some(self.cmp(other))
40    }
41}
42
43impl From<u32> for BlockHeight {
44    fn from(value: u32) -> Self {
45        BlockHeight(value)
46    }
47}
48
49impl From<BlockHeight> for u32 {
50    fn from(value: BlockHeight) -> u32 {
51        value.0
52    }
53}
54
55impl TryFrom<u64> for BlockHeight {
56    type Error = std::num::TryFromIntError;
57
58    fn try_from(value: u64) -> Result<Self, Self::Error> {
59        u32::try_from(value).map(BlockHeight)
60    }
61}
62
63impl From<BlockHeight> for u64 {
64    fn from(value: BlockHeight) -> u64 {
65        value.0 as u64
66    }
67}
68
69impl TryFrom<i32> for BlockHeight {
70    type Error = std::num::TryFromIntError;
71
72    fn try_from(value: i32) -> Result<Self, Self::Error> {
73        u32::try_from(value).map(BlockHeight)
74    }
75}
76
77impl TryFrom<i64> for BlockHeight {
78    type Error = std::num::TryFromIntError;
79
80    fn try_from(value: i64) -> Result<Self, Self::Error> {
81        u32::try_from(value).map(BlockHeight)
82    }
83}
84
85impl From<BlockHeight> for i64 {
86    fn from(value: BlockHeight) -> i64 {
87        value.0 as i64
88    }
89}
90
91impl Add<u32> for BlockHeight {
92    type Output = Self;
93
94    fn add(self, other: u32) -> Self {
95        BlockHeight(self.0 + other)
96    }
97}
98
99impl Add for BlockHeight {
100    type Output = Self;
101
102    fn add(self, other: Self) -> Self {
103        self + other.0
104    }
105}
106
107impl Sub<u32> for BlockHeight {
108    type Output = Self;
109
110    fn sub(self, other: u32) -> Self {
111        if other > self.0 {
112            panic!("Subtraction resulted in negative block height.");
113        }
114
115        BlockHeight(self.0 - other)
116    }
117}
118
119impl Sub for BlockHeight {
120    type Output = Self;
121
122    fn sub(self, other: Self) -> Self {
123        self - other.0
124    }
125}
126
127/// Zcash consensus parameters.
128pub trait Parameters: Clone {
129    /// Returns the activation height for a particular network upgrade,
130    /// if an activation height has been set.
131    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight>;
132
133    /// Determines whether the specified network upgrade is active as of the
134    /// provided block height on the network to which this Parameters value applies.
135    fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool {
136        self.activation_height(nu).map_or(false, |h| h <= height)
137    }
138
139    /// The coin type for ZEC, as defined by [SLIP 44].
140    ///
141    /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
142    fn coin_type(&self) -> u32;
143
144    /// Returns the standard network constant for address encoding. Returns
145    /// 'None' for nonstandard networks.
146    fn address_network(&self) -> Option<zcash_address::Network>;
147
148    /// Returns the human-readable prefix for Bech32-encoded Sapling extended spending keys
149    /// the network to which this Parameters value applies.
150    ///
151    /// Defined in [ZIP 32].
152    ///
153    /// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
154    /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst
155    fn hrp_sapling_extended_spending_key(&self) -> &str;
156
157    /// Returns the human-readable prefix for Bech32-encoded Sapling extended full
158    /// viewing keys for the network to which this Parameters value applies.
159    ///
160    /// Defined in [ZIP 32].
161    ///
162    /// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
163    /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst
164    fn hrp_sapling_extended_full_viewing_key(&self) -> &str;
165
166    /// Returns the Bech32-encoded human-readable prefix for Sapling payment addresses
167    /// viewing keys for the network to which this Parameters value applies.
168    ///
169    /// Defined in section 5.6.4 of the [Zcash Protocol Specification].
170    ///
171    /// [`PaymentAddress`]: zcash_primitives::primitives::PaymentAddress
172    /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf
173    fn hrp_sapling_payment_address(&self) -> &str;
174
175    /// Returns the human-readable prefix for Base58Check-encoded transparent
176    /// pay-to-public-key-hash payment addresses for the network to which this Parameters value
177    /// applies.
178    ///
179    /// [`TransparentAddress::PublicKey`]: zcash_primitives::legacy::TransparentAddress::PublicKey
180    fn b58_pubkey_address_prefix(&self) -> [u8; 2];
181
182    /// Returns the human-readable prefix for Base58Check-encoded transparent pay-to-script-hash
183    /// payment addresses for the network to which this Parameters value applies.
184    ///
185    /// [`TransparentAddress::Script`]: zcash_primitives::legacy::TransparentAddress::Script
186    fn b58_script_address_prefix(&self) -> [u8; 2];
187}
188
189/// Marker struct for the production network.
190#[derive(PartialEq, Copy, Clone, Debug)]
191pub struct MainNetwork;
192
193pub const MAIN_NETWORK: MainNetwork = MainNetwork;
194
195impl Parameters for MainNetwork {
196    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
197        match nu {
198            NetworkUpgrade::Overwinter => Some(BlockHeight(347_500)),
199            NetworkUpgrade::Sapling => Some(BlockHeight(419_200)),
200            NetworkUpgrade::Blossom => Some(BlockHeight(653_600)),
201            NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)),
202            NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)),
203            NetworkUpgrade::Nu5 => Some(BlockHeight(1_687_104)),
204            #[cfg(feature = "zfuture")]
205            NetworkUpgrade::ZFuture => None,
206        }
207    }
208
209    fn coin_type(&self) -> u32 {
210        constants::mainnet::COIN_TYPE
211    }
212
213    fn address_network(&self) -> Option<zcash_address::Network> {
214        Some(zcash_address::Network::Main)
215    }
216
217    fn hrp_sapling_extended_spending_key(&self) -> &str {
218        constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY
219    }
220
221    fn hrp_sapling_extended_full_viewing_key(&self) -> &str {
222        constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY
223    }
224
225    fn hrp_sapling_payment_address(&self) -> &str {
226        constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS
227    }
228
229    fn b58_pubkey_address_prefix(&self) -> [u8; 2] {
230        constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX
231    }
232
233    fn b58_script_address_prefix(&self) -> [u8; 2] {
234        constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX
235    }
236}
237
238/// Marker struct for the test network.
239#[derive(PartialEq, Copy, Clone, Debug)]
240pub struct TestNetwork;
241
242pub const TEST_NETWORK: TestNetwork = TestNetwork;
243
244impl Parameters for TestNetwork {
245    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
246        match nu {
247            NetworkUpgrade::Overwinter => Some(BlockHeight(207_500)),
248            NetworkUpgrade::Sapling => Some(BlockHeight(280_000)),
249            NetworkUpgrade::Blossom => Some(BlockHeight(584_000)),
250            NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)),
251            NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)),
252            NetworkUpgrade::Nu5 => Some(BlockHeight(1_842_420)),
253            #[cfg(feature = "zfuture")]
254            NetworkUpgrade::ZFuture => None,
255        }
256    }
257
258    fn coin_type(&self) -> u32 {
259        constants::testnet::COIN_TYPE
260    }
261
262    fn address_network(&self) -> Option<zcash_address::Network> {
263        Some(zcash_address::Network::Test)
264    }
265
266    fn hrp_sapling_extended_spending_key(&self) -> &str {
267        constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY
268    }
269
270    fn hrp_sapling_extended_full_viewing_key(&self) -> &str {
271        constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY
272    }
273
274    fn hrp_sapling_payment_address(&self) -> &str {
275        constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS
276    }
277
278    fn b58_pubkey_address_prefix(&self) -> [u8; 2] {
279        constants::testnet::B58_PUBKEY_ADDRESS_PREFIX
280    }
281
282    fn b58_script_address_prefix(&self) -> [u8; 2] {
283        constants::testnet::B58_SCRIPT_ADDRESS_PREFIX
284    }
285}
286
287#[derive(PartialEq, Copy, Clone, Debug)]
288pub enum Network {
289    MainNetwork,
290    TestNetwork,
291}
292
293impl Parameters for Network {
294    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
295        match self {
296            Network::MainNetwork => MAIN_NETWORK.activation_height(nu),
297            Network::TestNetwork => TEST_NETWORK.activation_height(nu),
298        }
299    }
300
301    fn coin_type(&self) -> u32 {
302        match self {
303            Network::MainNetwork => MAIN_NETWORK.coin_type(),
304            Network::TestNetwork => TEST_NETWORK.coin_type(),
305        }
306    }
307
308    fn address_network(&self) -> Option<zcash_address::Network> {
309        match self {
310            Network::MainNetwork => Some(zcash_address::Network::Main),
311            Network::TestNetwork => Some(zcash_address::Network::Test),
312        }
313    }
314
315    fn hrp_sapling_extended_spending_key(&self) -> &str {
316        match self {
317            Network::MainNetwork => MAIN_NETWORK.hrp_sapling_extended_spending_key(),
318            Network::TestNetwork => TEST_NETWORK.hrp_sapling_extended_spending_key(),
319        }
320    }
321
322    fn hrp_sapling_extended_full_viewing_key(&self) -> &str {
323        match self {
324            Network::MainNetwork => MAIN_NETWORK.hrp_sapling_extended_full_viewing_key(),
325            Network::TestNetwork => TEST_NETWORK.hrp_sapling_extended_full_viewing_key(),
326        }
327    }
328
329    fn hrp_sapling_payment_address(&self) -> &str {
330        match self {
331            Network::MainNetwork => MAIN_NETWORK.hrp_sapling_payment_address(),
332            Network::TestNetwork => TEST_NETWORK.hrp_sapling_payment_address(),
333        }
334    }
335
336    fn b58_pubkey_address_prefix(&self) -> [u8; 2] {
337        match self {
338            Network::MainNetwork => MAIN_NETWORK.b58_pubkey_address_prefix(),
339            Network::TestNetwork => TEST_NETWORK.b58_pubkey_address_prefix(),
340        }
341    }
342
343    fn b58_script_address_prefix(&self) -> [u8; 2] {
344        match self {
345            Network::MainNetwork => MAIN_NETWORK.b58_script_address_prefix(),
346            Network::TestNetwork => TEST_NETWORK.b58_script_address_prefix(),
347        }
348    }
349}
350
351/// An event that occurs at a specified height on the Zcash chain, at which point the
352/// consensus rules enforced by the network are altered.
353///
354/// See [ZIP 200](https://zips.z.cash/zip-0200) for more details.
355#[derive(Clone, Copy, Debug)]
356pub enum NetworkUpgrade {
357    /// The [Overwinter] network upgrade.
358    ///
359    /// [Overwinter]: https://z.cash/upgrade/overwinter/
360    Overwinter,
361    /// The [Sapling] network upgrade.
362    ///
363    /// [Sapling]: https://z.cash/upgrade/sapling/
364    Sapling,
365    /// The [Blossom] network upgrade.
366    ///
367    /// [Blossom]: https://z.cash/upgrade/blossom/
368    Blossom,
369    /// The [Heartwood] network upgrade.
370    ///
371    /// [Heartwood]: https://z.cash/upgrade/heartwood/
372    Heartwood,
373    /// The [Canopy] network upgrade.
374    ///
375    /// [Canopy]: https://z.cash/upgrade/canopy/
376    Canopy,
377    /// The [Nu5] network upgrade.
378    ///
379    /// [Nu5]: https://z.cash/upgrade/nu5/
380    Nu5,
381    /// The ZFUTURE network upgrade.
382    ///
383    /// This upgrade is expected never to activate on mainnet;
384    /// it is intended for use in integration testing of functionality
385    /// that is a candidate for integration in a future network upgrade.
386    #[cfg(feature = "zfuture")]
387    ZFuture,
388}
389
390impl fmt::Display for NetworkUpgrade {
391    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392        match self {
393            NetworkUpgrade::Overwinter => write!(f, "Overwinter"),
394            NetworkUpgrade::Sapling => write!(f, "Sapling"),
395            NetworkUpgrade::Blossom => write!(f, "Blossom"),
396            NetworkUpgrade::Heartwood => write!(f, "Heartwood"),
397            NetworkUpgrade::Canopy => write!(f, "Canopy"),
398            NetworkUpgrade::Nu5 => write!(f, "Nu5"),
399            #[cfg(feature = "zfuture")]
400            NetworkUpgrade::ZFuture => write!(f, "ZFUTURE"),
401        }
402    }
403}
404
405impl NetworkUpgrade {
406    fn branch_id(self) -> BranchId {
407        match self {
408            NetworkUpgrade::Overwinter => BranchId::Overwinter,
409            NetworkUpgrade::Sapling => BranchId::Sapling,
410            NetworkUpgrade::Blossom => BranchId::Blossom,
411            NetworkUpgrade::Heartwood => BranchId::Heartwood,
412            NetworkUpgrade::Canopy => BranchId::Canopy,
413            NetworkUpgrade::Nu5 => BranchId::Nu5,
414            #[cfg(feature = "zfuture")]
415            NetworkUpgrade::ZFuture => BranchId::ZFuture,
416        }
417    }
418}
419
420/// The network upgrades on the Zcash chain in order of activation.
421///
422/// This order corresponds to the activation heights, but because Rust enums are
423/// full-fledged algebraic data types, we need to define it manually.
424const UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[
425    NetworkUpgrade::Overwinter,
426    NetworkUpgrade::Sapling,
427    NetworkUpgrade::Blossom,
428    NetworkUpgrade::Heartwood,
429    NetworkUpgrade::Canopy,
430    NetworkUpgrade::Nu5,
431];
432
433pub const ZIP212_GRACE_PERIOD: u32 = 32256;
434
435/// A globally-unique identifier for a set of consensus rules within the Zcash chain.
436///
437/// Each branch ID in this enum corresponds to one of the epochs between a pair of Zcash
438/// network upgrades. For example, `BranchId::Overwinter` corresponds to the blocks
439/// starting at Overwinter activation, and ending the block before Sapling activation.
440///
441/// The main use of the branch ID is in signature generation: transactions commit to a
442/// specific branch ID by including it as part of [`signature_hash`]. This ensures
443/// two-way replay protection for transactions across network upgrades.
444///
445/// See [ZIP 200](https://zips.z.cash/zip-0200) for more details.
446///
447/// [`signature_hash`]: crate::transaction::sighash::signature_hash
448#[derive(Clone, Copy, Debug, PartialEq)]
449pub enum BranchId {
450    /// The consensus rules at the launch of Zcash.
451    Sprout,
452    /// The consensus rules deployed by [`NetworkUpgrade::Overwinter`].
453    Overwinter,
454    /// The consensus rules deployed by [`NetworkUpgrade::Sapling`].
455    Sapling,
456    /// The consensus rules deployed by [`NetworkUpgrade::Blossom`].
457    Blossom,
458    /// The consensus rules deployed by [`NetworkUpgrade::Heartwood`].
459    Heartwood,
460    /// The consensus rules deployed by [`NetworkUpgrade::Canopy`].
461    Canopy,
462    /// The consensus rules deployed by [`NetworkUpgrade::Nu5`].
463    Nu5,
464    /// Candidates for future consensus rules; this branch will never
465    /// activate on mainnet.
466    #[cfg(feature = "zfuture")]
467    ZFuture,
468}
469
470impl TryFrom<u32> for BranchId {
471    type Error = &'static str;
472
473    fn try_from(value: u32) -> Result<Self, Self::Error> {
474        match value {
475            0 => Ok(BranchId::Sprout),
476            0x5ba8_1b19 => Ok(BranchId::Overwinter),
477            0x76b8_09bb => Ok(BranchId::Sapling),
478            0x2bb4_0e60 => Ok(BranchId::Blossom),
479            0xf5b9_230b => Ok(BranchId::Heartwood),
480            0xe9ff_75a6 => Ok(BranchId::Canopy),
481            0xc2d6_d0b4 => Ok(BranchId::Nu5),
482            #[cfg(feature = "zfuture")]
483            0xffff_ffff => Ok(BranchId::ZFuture),
484            _ => Err("Unknown consensus branch ID"),
485        }
486    }
487}
488
489impl From<BranchId> for u32 {
490    fn from(consensus_branch_id: BranchId) -> u32 {
491        match consensus_branch_id {
492            BranchId::Sprout => 0,
493            BranchId::Overwinter => 0x5ba8_1b19,
494            BranchId::Sapling => 0x76b8_09bb,
495            BranchId::Blossom => 0x2bb4_0e60,
496            BranchId::Heartwood => 0xf5b9_230b,
497            BranchId::Canopy => 0xe9ff_75a6,
498            BranchId::Nu5 => 0xc2d6_d0b4,
499            #[cfg(feature = "zfuture")]
500            BranchId::ZFuture => 0xffff_ffff,
501        }
502    }
503}
504
505impl BranchId {
506    /// Returns the branch ID corresponding to the consensus rule set that is active at
507    /// the given height.
508    ///
509    /// This is the branch ID that should be used when creating transactions.
510    pub fn for_height<P: Parameters>(parameters: &P, height: BlockHeight) -> Self {
511        for nu in UPGRADES_IN_ORDER.iter().rev() {
512            if parameters.is_nu_active(*nu, height) {
513                return nu.branch_id();
514            }
515        }
516
517        // Sprout rules apply before any network upgrade
518        BranchId::Sprout
519    }
520
521    /// Returns the range of heights for the consensus epoch associated with this branch id.
522    ///
523    /// The resulting tuple implements the [`RangeBounds<BlockHeight>`] trait.
524    pub fn height_range<P: Parameters>(&self, params: &P) -> Option<impl RangeBounds<BlockHeight>> {
525        self.height_bounds(params).map(|(lower, upper)| {
526            (
527                Bound::Included(lower),
528                upper.map_or(Bound::Unbounded, Bound::Excluded),
529            )
530        })
531    }
532
533    /// Returns the range of heights for the consensus epoch associated with this branch id.
534    ///
535    /// The return type of this value is slightly more precise than [`Self::height_range`]:
536    /// - `Some((x, Some(y)))` means that the consensus rules corresponding to this branch id
537    ///   are in effect for the range `x..y`
538    /// - `Some((x, None))` means that the consensus rules corresponding to this branch id are
539    ///   in effect for the range `x..`
540    /// - `None` means that the consensus rules corresponding to this branch id are never in effect.
541    pub fn height_bounds<P: Parameters>(
542        &self,
543        params: &P,
544    ) -> Option<(BlockHeight, Option<BlockHeight>)> {
545        match self {
546            BranchId::Sprout => params
547                .activation_height(NetworkUpgrade::Overwinter)
548                .map(|upper| (BlockHeight(0), Some(upper))),
549            BranchId::Overwinter => params
550                .activation_height(NetworkUpgrade::Overwinter)
551                .map(|lower| (lower, params.activation_height(NetworkUpgrade::Sapling))),
552            BranchId::Sapling => params
553                .activation_height(NetworkUpgrade::Sapling)
554                .map(|lower| (lower, params.activation_height(NetworkUpgrade::Blossom))),
555            BranchId::Blossom => params
556                .activation_height(NetworkUpgrade::Blossom)
557                .map(|lower| (lower, params.activation_height(NetworkUpgrade::Heartwood))),
558            BranchId::Heartwood => params
559                .activation_height(NetworkUpgrade::Heartwood)
560                .map(|lower| (lower, params.activation_height(NetworkUpgrade::Canopy))),
561            BranchId::Canopy => params
562                .activation_height(NetworkUpgrade::Canopy)
563                .map(|lower| (lower, params.activation_height(NetworkUpgrade::Nu5))),
564            BranchId::Nu5 => params.activation_height(NetworkUpgrade::Nu5).map(|lower| {
565                #[cfg(feature = "zfuture")]
566                let upper = params.activation_height(NetworkUpgrade::ZFuture);
567                #[cfg(not(feature = "zfuture"))]
568                let upper = None;
569                (lower, upper)
570            }),
571            #[cfg(feature = "zfuture")]
572            BranchId::ZFuture => params
573                .activation_height(NetworkUpgrade::ZFuture)
574                .map(|lower| (lower, None)),
575        }
576    }
577
578    pub fn sprout_uses_groth_proofs(&self) -> bool {
579        !matches!(self, BranchId::Sprout | BranchId::Overwinter)
580    }
581}
582
583#[cfg(any(test, feature = "test-dependencies"))]
584pub mod testing {
585    use proptest::sample::select;
586    use proptest::strategy::{Just, Strategy};
587
588    use super::{BlockHeight, BranchId, Parameters};
589
590    pub fn arb_branch_id() -> impl Strategy<Value = BranchId> {
591        select(vec![
592            BranchId::Sprout,
593            BranchId::Overwinter,
594            BranchId::Sapling,
595            BranchId::Blossom,
596            BranchId::Heartwood,
597            BranchId::Canopy,
598            BranchId::Nu5,
599            #[cfg(feature = "zfuture")]
600            BranchId::ZFuture,
601        ])
602    }
603
604    pub fn arb_height<P: Parameters>(
605        branch_id: BranchId,
606        params: &P,
607    ) -> impl Strategy<Value = Option<BlockHeight>> {
608        branch_id
609            .height_bounds(params)
610            .map_or(Strategy::boxed(Just(None)), |(lower, upper)| {
611                Strategy::boxed(
612                    (lower.0..upper.map_or(std::u32::MAX, |u| u.0))
613                        .prop_map(|h| Some(BlockHeight(h))),
614                )
615            })
616    }
617}
618
619#[cfg(test)]
620mod tests {
621    use std::convert::TryFrom;
622
623    use super::{
624        BlockHeight, BranchId, NetworkUpgrade, Parameters, MAIN_NETWORK, UPGRADES_IN_ORDER,
625    };
626
627    #[test]
628    fn nu_ordering() {
629        for i in 1..UPGRADES_IN_ORDER.len() {
630            let nu_a = UPGRADES_IN_ORDER[i - 1];
631            let nu_b = UPGRADES_IN_ORDER[i];
632            match (
633                MAIN_NETWORK.activation_height(nu_a),
634                MAIN_NETWORK.activation_height(nu_b),
635            ) {
636                (Some(a), Some(b)) if a < b => (),
637                (Some(_), None) => (),
638                (None, None) => (),
639                _ => panic!(
640                    "{} should not be before {} in UPGRADES_IN_ORDER",
641                    nu_a, nu_b
642                ),
643            }
644        }
645    }
646
647    #[test]
648    fn nu_is_active() {
649        assert!(!MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(0)));
650        assert!(!MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_499)));
651        assert!(MAIN_NETWORK.is_nu_active(NetworkUpgrade::Overwinter, BlockHeight(347_500)));
652    }
653
654    #[test]
655    fn branch_id_from_u32() {
656        assert_eq!(BranchId::try_from(0), Ok(BranchId::Sprout));
657        assert!(BranchId::try_from(1).is_err());
658    }
659
660    #[test]
661    fn branch_id_for_height() {
662        assert_eq!(
663            BranchId::for_height(&MAIN_NETWORK, BlockHeight(0)),
664            BranchId::Sprout,
665        );
666        assert_eq!(
667            BranchId::for_height(&MAIN_NETWORK, BlockHeight(419_199)),
668            BranchId::Overwinter,
669        );
670        assert_eq!(
671            BranchId::for_height(&MAIN_NETWORK, BlockHeight(419_200)),
672            BranchId::Sapling,
673        );
674        assert_eq!(
675            BranchId::for_height(&MAIN_NETWORK, BlockHeight(903_000)),
676            BranchId::Heartwood,
677        );
678        assert_eq!(
679            BranchId::for_height(&MAIN_NETWORK, BlockHeight(1_046_400)),
680            BranchId::Canopy,
681        );
682        assert_eq!(
683            BranchId::for_height(&MAIN_NETWORK, BlockHeight(1_687_104)),
684            BranchId::Nu5,
685        );
686        assert_eq!(
687            BranchId::for_height(&MAIN_NETWORK, BlockHeight(5_000_000)),
688            BranchId::Nu5,
689        );
690    }
691}