zebra_chain/parameters/network/
testnet.rs

1//! Types and implementation for Testnet consensus parameters
2use std::{collections::BTreeMap, fmt, sync::Arc};
3
4use crate::{
5    block::{self, Height, HeightDiff},
6    parameters::{
7        constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT},
8        network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
9        subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR},
10        Network, NetworkKind, NetworkUpgrade,
11    },
12    work::difficulty::{ExpandedDifficulty, U256},
13};
14
15use super::{
16    magic::Magic,
17    subsidy::{
18        FundingStreamReceiver, FundingStreamRecipient, FundingStreams,
19        BLOSSOM_POW_TARGET_SPACING_RATIO, POST_BLOSSOM_HALVING_INTERVAL,
20        POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET,
21        PRE_BLOSSOM_HALVING_INTERVAL, PRE_NU6_FUNDING_STREAMS_MAINNET,
22        PRE_NU6_FUNDING_STREAMS_TESTNET,
23    },
24};
25
26/// The Regtest NU5 activation height in tests
27// TODO: Serialize testnet parameters in Config then remove this and use a configured NU5 activation height.
28#[cfg(any(test, feature = "proptest-impl"))]
29pub const REGTEST_NU5_ACTIVATION_HEIGHT: u32 = 100;
30
31/// Reserved network names that should not be allowed for configured Testnets.
32pub const RESERVED_NETWORK_NAMES: [&str; 6] = [
33    "Mainnet",
34    "Testnet",
35    "Regtest",
36    "MainnetKind",
37    "TestnetKind",
38    "RegtestKind",
39];
40
41/// Maximum length for a configured network name.
42pub const MAX_NETWORK_NAME_LENGTH: usize = 30;
43
44/// Maximum length for a configured human-readable prefix.
45pub const MAX_HRP_LENGTH: usize = 30;
46
47/// The block hash of the Regtest genesis block, `zcash-cli -regtest getblockhash 0`
48const REGTEST_GENESIS_HASH: &str =
49    "029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327";
50
51/// The block hash of the Testnet genesis block, `zcash-cli -testnet getblockhash 0`
52const TESTNET_GENESIS_HASH: &str =
53    "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38";
54
55/// The halving height interval in the regtest is 6 hours.
56/// [zcashd regtest halving interval](https://github.com/zcash/zcash/blob/v5.10.0/src/consensus/params.h#L252)
57const PRE_BLOSSOM_REGTEST_HALVING_INTERVAL: HeightDiff = 144;
58
59/// Configurable funding stream recipient for configured Testnets.
60#[derive(Serialize, Deserialize, Clone, Debug)]
61#[serde(deny_unknown_fields)]
62pub struct ConfiguredFundingStreamRecipient {
63    /// Funding stream receiver, see [`FundingStreams::recipients`] for more details.
64    pub receiver: FundingStreamReceiver,
65    /// The numerator for each funding stream receiver category, see [`FundingStreamRecipient::numerator`] for more details.
66    pub numerator: u64,
67    /// Addresses for the funding stream recipient, see [`FundingStreamRecipient::addresses`] for more details.
68    pub addresses: Option<Vec<String>>,
69}
70
71impl ConfiguredFundingStreamRecipient {
72    /// Converts a [`ConfiguredFundingStreamRecipient`] to a [`FundingStreamReceiver`] and [`FundingStreamRecipient`].
73    pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) {
74        (
75            self.receiver,
76            FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()),
77        )
78    }
79}
80
81/// Configurable funding streams for configured Testnets.
82#[derive(Serialize, Deserialize, Clone, Default, Debug)]
83#[serde(deny_unknown_fields)]
84pub struct ConfiguredFundingStreams {
85    /// Start and end height for funding streams see [`FundingStreams::height_range`] for more details.
86    pub height_range: Option<std::ops::Range<Height>>,
87    /// Funding stream recipients, see [`FundingStreams::recipients`] for more details.
88    pub recipients: Option<Vec<ConfiguredFundingStreamRecipient>>,
89}
90
91impl From<&FundingStreams> for ConfiguredFundingStreams {
92    fn from(value: &FundingStreams) -> Self {
93        Self {
94            height_range: Some(value.height_range().clone()),
95            recipients: Some(
96                value
97                    .recipients()
98                    .iter()
99                    .map(|(receiver, recipient)| ConfiguredFundingStreamRecipient {
100                        receiver: *receiver,
101                        numerator: recipient.numerator(),
102                        addresses: Some(
103                            recipient
104                                .addresses()
105                                .iter()
106                                .map(ToString::to_string)
107                                .collect(),
108                        ),
109                    })
110                    .collect(),
111            ),
112        }
113    }
114}
115
116impl From<&BTreeMap<Height, NetworkUpgrade>> for ConfiguredActivationHeights {
117    fn from(activation_heights: &BTreeMap<Height, NetworkUpgrade>) -> Self {
118        let mut configured_activation_heights = ConfiguredActivationHeights::default();
119
120        for (height, network_upgrade) in activation_heights.iter() {
121            let field = match network_upgrade {
122                NetworkUpgrade::BeforeOverwinter => {
123                    &mut configured_activation_heights.before_overwinter
124                }
125                NetworkUpgrade::Overwinter => &mut configured_activation_heights.overwinter,
126                NetworkUpgrade::Sapling => &mut configured_activation_heights.sapling,
127                NetworkUpgrade::Blossom => &mut configured_activation_heights.blossom,
128                NetworkUpgrade::Heartwood => &mut configured_activation_heights.heartwood,
129                NetworkUpgrade::Canopy => &mut configured_activation_heights.canopy,
130                NetworkUpgrade::Nu5 => &mut configured_activation_heights.nu5,
131                NetworkUpgrade::Nu6 => &mut configured_activation_heights.nu6,
132                NetworkUpgrade::Nu6_1 => &mut configured_activation_heights.nu6_1,
133                NetworkUpgrade::Nu7 => &mut configured_activation_heights.nu7,
134                NetworkUpgrade::Genesis => {
135                    continue;
136                }
137            };
138
139            *field = Some(height.0)
140        }
141
142        configured_activation_heights
143    }
144}
145
146impl ConfiguredFundingStreams {
147    /// Returns an empty [`ConfiguredFundingStreams`].
148    fn empty() -> Self {
149        Self {
150            height_range: None,
151            recipients: Some(Vec::new()),
152        }
153    }
154
155    /// Converts a [`ConfiguredFundingStreams`] to a [`FundingStreams`], using the provided default values
156    /// if `height_range` or `recipients` are None.
157    fn convert_with_default(
158        self,
159        default_funding_streams: FundingStreams,
160        parameters_builder: &ParametersBuilder,
161    ) -> FundingStreams {
162        let network = parameters_builder.to_network_unchecked();
163        let height_range = self
164            .height_range
165            .unwrap_or(default_funding_streams.height_range().clone());
166
167        let recipients = self
168            .recipients
169            .map(|recipients| {
170                recipients
171                    .into_iter()
172                    .map(ConfiguredFundingStreamRecipient::into_recipient)
173                    .collect()
174            })
175            .unwrap_or(default_funding_streams.recipients().clone());
176
177        assert!(
178            height_range.start < height_range.end,
179            "funding stream end height must be above start height"
180        );
181
182        let funding_streams = FundingStreams::new(height_range.clone(), recipients);
183
184        check_funding_stream_address_period(&funding_streams, &network);
185
186        // check that sum of receiver numerators is valid.
187
188        let sum_numerators: u64 = funding_streams
189            .recipients()
190            .values()
191            .map(|r| r.numerator())
192            .sum();
193
194        assert!(
195            sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR,
196            "sum of funding stream numerators must not be \
197         greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}"
198        );
199
200        funding_streams
201    }
202}
203
204/// Checks that the provided [`FundingStreams`] has sufficient recipient addresses for the
205/// funding stream address period of the provided [`Network`].
206fn check_funding_stream_address_period(funding_streams: &FundingStreams, network: &Network) {
207    let height_range = funding_streams.height_range();
208    let expected_min_num_addresses =
209        1u32.checked_add(funding_stream_address_period(
210            height_range
211                .end
212                .previous()
213                .expect("end height must be above start height and genesis height"),
214            network,
215        ))
216        .expect("no overflow should happen in this sum")
217        .checked_sub(funding_stream_address_period(height_range.start, network))
218        .expect("no overflow should happen in this sub") as usize;
219
220    for (&receiver, recipient) in funding_streams.recipients() {
221        if receiver == FundingStreamReceiver::Deferred {
222            // The `Deferred` receiver doesn't need any addresses.
223            continue;
224        }
225
226        assert!(
227            recipient.addresses().len() >= expected_min_num_addresses,
228            "recipients must have a sufficient number of addresses for height range, \
229         minimum num addresses required: {expected_min_num_addresses}"
230        );
231
232        for address in recipient.addresses() {
233            assert_eq!(
234                address.network_kind(),
235                NetworkKind::Testnet,
236                "configured funding stream addresses must be for Testnet"
237            );
238        }
239    }
240}
241
242/// Configurable activation heights for Regtest and configured Testnets.
243#[derive(Serialize, Deserialize, Default, Clone)]
244#[serde(rename_all = "PascalCase", deny_unknown_fields)]
245pub struct ConfiguredActivationHeights {
246    /// Activation height for `BeforeOverwinter` network upgrade.
247    pub before_overwinter: Option<u32>,
248    /// Activation height for `Overwinter` network upgrade.
249    pub overwinter: Option<u32>,
250    /// Activation height for `Sapling` network upgrade.
251    pub sapling: Option<u32>,
252    /// Activation height for `Blossom` network upgrade.
253    pub blossom: Option<u32>,
254    /// Activation height for `Heartwood` network upgrade.
255    pub heartwood: Option<u32>,
256    /// Activation height for `Canopy` network upgrade.
257    pub canopy: Option<u32>,
258    /// Activation height for `NU5` network upgrade.
259    #[serde(rename = "NU5")]
260    pub nu5: Option<u32>,
261    /// Activation height for `NU6` network upgrade.
262    #[serde(rename = "NU6")]
263    pub nu6: Option<u32>,
264    /// Activation height for `NU6.1` network upgrade.
265    #[serde(rename = "NU6.1")]
266    pub nu6_1: Option<u32>,
267    /// Activation height for `NU7` network upgrade.
268    #[serde(rename = "NU7")]
269    pub nu7: Option<u32>,
270}
271
272/// Builder for the [`Parameters`] struct.
273#[derive(Clone, Debug, Eq, PartialEq)]
274pub struct ParametersBuilder {
275    /// The name of this network to be used by the `Display` trait impl.
276    network_name: String,
277    /// The network magic, acts as an identifier for the network.
278    network_magic: Magic,
279    /// The genesis block hash
280    genesis_hash: block::Hash,
281    /// The network upgrade activation heights for this network, see [`Parameters::activation_heights`] for more details.
282    activation_heights: BTreeMap<Height, NetworkUpgrade>,
283    /// Slow start interval for this network
284    slow_start_interval: Height,
285    /// Pre-NU6 funding streams for this network
286    pre_nu6_funding_streams: FundingStreams,
287    /// Post-NU6 funding streams for this network
288    post_nu6_funding_streams: FundingStreams,
289    /// A flag indicating whether to allow changes to fields that affect
290    /// the funding stream address period.
291    should_lock_funding_stream_address_period: bool,
292    /// Target difficulty limit for this network
293    target_difficulty_limit: ExpandedDifficulty,
294    /// A flag for disabling proof-of-work checks when Zebra is validating blocks
295    disable_pow: bool,
296    /// Whether to allow transactions with transparent outputs to spend coinbase outputs,
297    /// similar to `fCoinbaseMustBeShielded` in zcashd.
298    should_allow_unshielded_coinbase_spends: bool,
299    /// The pre-Blossom halving interval for this network
300    pre_blossom_halving_interval: HeightDiff,
301    /// The post-Blossom halving interval for this network
302    post_blossom_halving_interval: HeightDiff,
303}
304
305impl Default for ParametersBuilder {
306    /// Creates a [`ParametersBuilder`] with all of the default Testnet parameters except `network_name`.
307    fn default() -> Self {
308        Self {
309            network_name: "UnknownTestnet".to_string(),
310            network_magic: magics::TESTNET,
311            // # Correctness
312            //
313            // `Genesis` network upgrade activation height must always be 0
314            activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
315            genesis_hash: TESTNET_GENESIS_HASH
316                .parse()
317                .expect("hard-coded hash parses"),
318            slow_start_interval: SLOW_START_INTERVAL,
319            // Testnet PoWLimit is defined as `2^251 - 1` on page 73 of the protocol specification:
320            // <https://zips.z.cash/protocol/protocol.pdf>
321            //
322            // `zcashd` converts the PoWLimit into a compact representation before
323            // using it to perform difficulty filter checks.
324            //
325            // The Zcash specification converts to compact for the default difficulty
326            // filter, but not for testnet minimum difficulty blocks. (ZIP 205 and
327            // ZIP 208 don't specify this conversion either.) See #1277 for details.
328            target_difficulty_limit: ExpandedDifficulty::from((U256::one() << 251) - 1)
329                .to_compact()
330                .to_expanded()
331                .expect("difficulty limits are valid expanded values"),
332            disable_pow: false,
333            pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(),
334            post_nu6_funding_streams: POST_NU6_FUNDING_STREAMS_TESTNET.clone(),
335            should_lock_funding_stream_address_period: false,
336            pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL,
337            post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL,
338            should_allow_unshielded_coinbase_spends: false,
339        }
340    }
341}
342
343impl ParametersBuilder {
344    /// Sets the network name to be used in the [`Parameters`] being built.
345    pub fn with_network_name(mut self, network_name: impl fmt::Display) -> Self {
346        self.network_name = network_name.to_string();
347
348        assert!(
349            !RESERVED_NETWORK_NAMES.contains(&self.network_name.as_str()),
350            "cannot use reserved network name '{network_name}' as configured Testnet name, reserved names: {RESERVED_NETWORK_NAMES:?}"
351        );
352
353        assert!(
354            self.network_name.len() <= MAX_NETWORK_NAME_LENGTH,
355            "network name {network_name} is too long, must be {MAX_NETWORK_NAME_LENGTH} characters or less"
356        );
357
358        assert!(
359            self.network_name
360                .chars()
361                .all(|x| x.is_alphanumeric() || x == '_'),
362            "network name must include only alphanumeric characters or '_'"
363        );
364
365        self
366    }
367
368    /// Sets the network name to be used in the [`Parameters`] being built.
369    pub fn with_network_magic(mut self, network_magic: Magic) -> Self {
370        assert!(
371            [magics::MAINNET, magics::REGTEST]
372                .into_iter()
373                .all(|reserved_magic| network_magic != reserved_magic),
374            "network magic should be distinct from reserved network magics"
375        );
376
377        self.network_magic = network_magic;
378
379        self
380    }
381
382    /// Parses the hex-encoded block hash and sets it as the genesis hash in the [`Parameters`] being built.
383    pub fn with_genesis_hash(mut self, genesis_hash: impl fmt::Display) -> Self {
384        self.genesis_hash = genesis_hash
385            .to_string()
386            .parse()
387            .expect("configured genesis hash must parse");
388        self
389    }
390
391    /// Checks that the provided network upgrade activation heights are in the correct order, then
392    /// sets them as the new network upgrade activation heights.
393    pub fn with_activation_heights(
394        mut self,
395        ConfiguredActivationHeights {
396            before_overwinter,
397            overwinter,
398            sapling,
399            blossom,
400            heartwood,
401            canopy,
402            nu5,
403            nu6,
404            nu6_1,
405            nu7,
406        }: ConfiguredActivationHeights,
407    ) -> Self {
408        use NetworkUpgrade::*;
409
410        if self.should_lock_funding_stream_address_period {
411            panic!("activation heights on ParametersBuilder must not be set after setting funding streams");
412        }
413
414        // # Correctness
415        //
416        // These must be in order so that later network upgrades overwrite prior ones
417        // if multiple network upgrades are configured with the same activation height.
418        let activation_heights: BTreeMap<_, _> = before_overwinter
419            .into_iter()
420            .map(|h| (h, BeforeOverwinter))
421            .chain(overwinter.into_iter().map(|h| (h, Overwinter)))
422            .chain(sapling.into_iter().map(|h| (h, Sapling)))
423            .chain(blossom.into_iter().map(|h| (h, Blossom)))
424            .chain(heartwood.into_iter().map(|h| (h, Heartwood)))
425            .chain(canopy.into_iter().map(|h| (h, Canopy)))
426            .chain(nu5.into_iter().map(|h| (h, Nu5)))
427            .chain(nu6.into_iter().map(|h| (h, Nu6)))
428            .chain(nu6_1.into_iter().map(|h| (h, Nu6_1)))
429            .chain(nu7.into_iter().map(|h| (h, Nu7)))
430            .map(|(h, nu)| (h.try_into().expect("activation height must be valid"), nu))
431            .collect();
432
433        let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect();
434
435        // Check that the provided network upgrade activation heights are in the same order by height as the default testnet activation heights
436        let mut activation_heights_iter = activation_heights.iter();
437        for expected_network_upgrade in NetworkUpgrade::iter() {
438            if !network_upgrades.contains(&expected_network_upgrade) {
439                continue;
440            } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
441                assert_ne!(
442                    height,
443                    Height(0),
444                    "Height(0) is reserved for the `Genesis` upgrade"
445                );
446
447                assert!(
448                    network_upgrade == expected_network_upgrade,
449                    "network upgrades must be activated in order specified by the protocol"
450                );
451            }
452        }
453
454        // # Correctness
455        //
456        // Height(0) must be reserved for the `NetworkUpgrade::Genesis`.
457        self.activation_heights.split_off(&Height(1));
458        self.activation_heights.extend(activation_heights);
459
460        self
461    }
462
463    /// Sets the slow start interval to be used in the [`Parameters`] being built.
464    pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
465        self.slow_start_interval = slow_start_interval;
466        self
467    }
468
469    /// Sets pre-NU6 funding streams to be used in the [`Parameters`] being built.
470    pub fn with_pre_nu6_funding_streams(
471        mut self,
472        funding_streams: ConfiguredFundingStreams,
473    ) -> Self {
474        self.pre_nu6_funding_streams =
475            funding_streams.convert_with_default(PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), &self);
476        self.should_lock_funding_stream_address_period = true;
477        self
478    }
479
480    /// Sets post-NU6 funding streams to be used in the [`Parameters`] being built.
481    pub fn with_post_nu6_funding_streams(
482        mut self,
483        funding_streams: ConfiguredFundingStreams,
484    ) -> Self {
485        self.post_nu6_funding_streams =
486            funding_streams.convert_with_default(POST_NU6_FUNDING_STREAMS_TESTNET.clone(), &self);
487        self.should_lock_funding_stream_address_period = true;
488        self
489    }
490
491    /// Sets the target difficulty limit to be used in the [`Parameters`] being built.
492    // TODO: Accept a hex-encoded String instead?
493    pub fn with_target_difficulty_limit(
494        mut self,
495        target_difficulty_limit: impl Into<ExpandedDifficulty>,
496    ) -> Self {
497        self.target_difficulty_limit = target_difficulty_limit
498            .into()
499            .to_compact()
500            .to_expanded()
501            .expect("difficulty limits are valid expanded values");
502        self
503    }
504
505    /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
506    pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
507        self.disable_pow = disable_pow;
508        self
509    }
510
511    /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
512    pub fn with_unshielded_coinbase_spends(
513        mut self,
514        should_allow_unshielded_coinbase_spends: bool,
515    ) -> Self {
516        self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends;
517        self
518    }
519
520    /// Sets the pre and post Blosssom halving intervals to be used in the [`Parameters`] being built.
521    pub fn with_halving_interval(mut self, pre_blossom_halving_interval: HeightDiff) -> Self {
522        if self.should_lock_funding_stream_address_period {
523            panic!("halving interval on ParametersBuilder must not be set after setting funding streams");
524        }
525
526        self.pre_blossom_halving_interval = pre_blossom_halving_interval;
527        self.post_blossom_halving_interval =
528            self.pre_blossom_halving_interval * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
529        self
530    }
531
532    /// Converts the builder to a [`Parameters`] struct
533    fn finish(self) -> Parameters {
534        let Self {
535            network_name,
536            network_magic,
537            genesis_hash,
538            activation_heights,
539            slow_start_interval,
540            pre_nu6_funding_streams,
541            post_nu6_funding_streams,
542            should_lock_funding_stream_address_period: _,
543            target_difficulty_limit,
544            disable_pow,
545            should_allow_unshielded_coinbase_spends,
546            pre_blossom_halving_interval,
547            post_blossom_halving_interval,
548        } = self;
549        Parameters {
550            network_name,
551            network_magic,
552            genesis_hash,
553            activation_heights,
554            slow_start_interval,
555            slow_start_shift: Height(slow_start_interval.0 / 2),
556            pre_nu6_funding_streams,
557            post_nu6_funding_streams,
558            target_difficulty_limit,
559            disable_pow,
560            should_allow_unshielded_coinbase_spends,
561            pre_blossom_halving_interval,
562            post_blossom_halving_interval,
563        }
564    }
565
566    /// Converts the builder to a configured [`Network::Testnet`]
567    fn to_network_unchecked(&self) -> Network {
568        Network::new_configured_testnet(self.clone().finish())
569    }
570
571    /// Checks funding streams and converts the builder to a configured [`Network::Testnet`]
572    pub fn to_network(self) -> Network {
573        let network = self.to_network_unchecked();
574
575        // Final check that the configured funding streams will be valid for these Testnet parameters.
576        // TODO: Always check funding stream address period once the testnet parameters are being serialized (#8920).
577        #[cfg(not(any(test, feature = "proptest-impl")))]
578        {
579            check_funding_stream_address_period(&self.pre_nu6_funding_streams, &network);
580            check_funding_stream_address_period(&self.post_nu6_funding_streams, &network);
581        }
582
583        network
584    }
585
586    /// Returns true if these [`Parameters`] should be compatible with the default Testnet parameters.
587    pub fn is_compatible_with_default_parameters(&self) -> bool {
588        let Self {
589            network_name: _,
590            network_magic,
591            genesis_hash,
592            activation_heights,
593            slow_start_interval,
594            pre_nu6_funding_streams,
595            post_nu6_funding_streams,
596            should_lock_funding_stream_address_period: _,
597            target_difficulty_limit,
598            disable_pow,
599            should_allow_unshielded_coinbase_spends,
600            pre_blossom_halving_interval,
601            post_blossom_halving_interval,
602        } = Self::default();
603
604        self.activation_heights == activation_heights
605            && self.network_magic == network_magic
606            && self.genesis_hash == genesis_hash
607            && self.slow_start_interval == slow_start_interval
608            && self.pre_nu6_funding_streams == pre_nu6_funding_streams
609            && self.post_nu6_funding_streams == post_nu6_funding_streams
610            && self.target_difficulty_limit == target_difficulty_limit
611            && self.disable_pow == disable_pow
612            && self.should_allow_unshielded_coinbase_spends
613                == should_allow_unshielded_coinbase_spends
614            && self.pre_blossom_halving_interval == pre_blossom_halving_interval
615            && self.post_blossom_halving_interval == post_blossom_halving_interval
616    }
617}
618
619/// Network consensus parameters for test networks such as Regtest and the default Testnet.
620#[derive(Clone, Debug, Eq, PartialEq)]
621pub struct Parameters {
622    /// The name of this network to be used by the `Display` trait impl.
623    network_name: String,
624    /// The network magic, acts as an identifier for the network.
625    network_magic: Magic,
626    /// The genesis block hash
627    genesis_hash: block::Hash,
628    /// The network upgrade activation heights for this network.
629    ///
630    /// Note: This value is ignored by `Network::activation_list()` when `zebra-chain` is
631    ///       compiled with the `zebra-test` feature flag AND the `TEST_FAKE_ACTIVATION_HEIGHTS`
632    ///       environment variable is set.
633    activation_heights: BTreeMap<Height, NetworkUpgrade>,
634    /// Slow start interval for this network
635    slow_start_interval: Height,
636    /// Slow start shift for this network, always half the slow start interval
637    slow_start_shift: Height,
638    /// Pre-NU6 funding streams for this network
639    pre_nu6_funding_streams: FundingStreams,
640    /// Post-NU6 funding streams for this network
641    post_nu6_funding_streams: FundingStreams,
642    /// Target difficulty limit for this network
643    target_difficulty_limit: ExpandedDifficulty,
644    /// A flag for disabling proof-of-work checks when Zebra is validating blocks
645    disable_pow: bool,
646    /// Whether to allow transactions with transparent outputs to spend coinbase outputs,
647    /// similar to `fCoinbaseMustBeShielded` in zcashd.
648    should_allow_unshielded_coinbase_spends: bool,
649    /// Pre-Blossom halving interval for this network
650    pre_blossom_halving_interval: HeightDiff,
651    /// Post-Blossom halving interval for this network
652    post_blossom_halving_interval: HeightDiff,
653}
654
655impl Default for Parameters {
656    /// Returns an instance of the default public testnet [`Parameters`].
657    fn default() -> Self {
658        Self {
659            network_name: "Testnet".to_string(),
660            ..Self::build().finish()
661        }
662    }
663}
664
665impl Parameters {
666    /// Creates a new [`ParametersBuilder`].
667    pub fn build() -> ParametersBuilder {
668        ParametersBuilder::default()
669    }
670
671    /// Accepts a [`ConfiguredActivationHeights`].
672    ///
673    /// Creates an instance of [`Parameters`] with `Regtest` values.
674    pub fn new_regtest(
675        ConfiguredActivationHeights { nu5, nu6, nu7, .. }: ConfiguredActivationHeights,
676    ) -> Self {
677        #[cfg(any(test, feature = "proptest-impl"))]
678        let nu5 = nu5.or(Some(100));
679
680        let parameters = Self::build()
681            .with_genesis_hash(REGTEST_GENESIS_HASH)
682            // This value is chosen to match zcashd, see: <https://github.com/zcash/zcash/blob/master/src/chainparams.cpp#L654>
683            .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))
684            .with_disable_pow(true)
685            .with_unshielded_coinbase_spends(true)
686            .with_slow_start_interval(Height::MIN)
687            // Removes default Testnet activation heights if not configured,
688            // most network upgrades are disabled by default for Regtest in zcashd
689            .with_activation_heights(ConfiguredActivationHeights {
690                canopy: Some(1),
691                nu5,
692                nu6,
693                nu7,
694                ..Default::default()
695            })
696            .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL);
697
698        // TODO: Always clear funding streams on Regtest once the testnet parameters are being serialized (#8920).
699        // #[cfg(not(any(test, feature = "proptest-impl")))]
700        let parameters = parameters
701            .with_pre_nu6_funding_streams(ConfiguredFundingStreams::empty())
702            .with_post_nu6_funding_streams(ConfiguredFundingStreams::empty());
703
704        Self {
705            network_name: "Regtest".to_string(),
706            network_magic: magics::REGTEST,
707            ..parameters.finish()
708        }
709    }
710
711    /// Returns true if the instance of [`Parameters`] represents the default public Testnet.
712    pub fn is_default_testnet(&self) -> bool {
713        self == &Self::default()
714    }
715
716    /// Returns true if the instance of [`Parameters`] represents Regtest.
717    pub fn is_regtest(&self) -> bool {
718        if self.network_magic != magics::REGTEST {
719            return false;
720        }
721
722        let Self {
723            network_name,
724            // Already checked network magic above
725            network_magic: _,
726            genesis_hash,
727            // Activation heights are configurable on Regtest
728            activation_heights: _,
729            slow_start_interval,
730            slow_start_shift,
731            pre_nu6_funding_streams,
732            post_nu6_funding_streams,
733            target_difficulty_limit,
734            disable_pow,
735            should_allow_unshielded_coinbase_spends,
736            pre_blossom_halving_interval,
737            post_blossom_halving_interval,
738        } = Self::new_regtest(Default::default());
739
740        self.network_name == network_name
741            && self.genesis_hash == genesis_hash
742            && self.slow_start_interval == slow_start_interval
743            && self.slow_start_shift == slow_start_shift
744            && self.pre_nu6_funding_streams == pre_nu6_funding_streams
745            && self.post_nu6_funding_streams == post_nu6_funding_streams
746            && self.target_difficulty_limit == target_difficulty_limit
747            && self.disable_pow == disable_pow
748            && self.should_allow_unshielded_coinbase_spends
749                == should_allow_unshielded_coinbase_spends
750            && self.pre_blossom_halving_interval == pre_blossom_halving_interval
751            && self.post_blossom_halving_interval == post_blossom_halving_interval
752    }
753
754    /// Returns the network name
755    pub fn network_name(&self) -> &str {
756        &self.network_name
757    }
758
759    /// Returns the network magic
760    pub fn network_magic(&self) -> Magic {
761        self.network_magic
762    }
763
764    /// Returns the genesis hash
765    pub fn genesis_hash(&self) -> block::Hash {
766        self.genesis_hash
767    }
768
769    /// Returns the network upgrade activation heights
770    pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
771        &self.activation_heights
772    }
773
774    /// Returns slow start interval for this network
775    pub fn slow_start_interval(&self) -> Height {
776        self.slow_start_interval
777    }
778
779    /// Returns slow start shift for this network
780    pub fn slow_start_shift(&self) -> Height {
781        self.slow_start_shift
782    }
783
784    /// Returns pre-NU6 funding streams for this network
785    pub fn pre_nu6_funding_streams(&self) -> &FundingStreams {
786        &self.pre_nu6_funding_streams
787    }
788
789    /// Returns post-NU6 funding streams for this network
790    pub fn post_nu6_funding_streams(&self) -> &FundingStreams {
791        &self.post_nu6_funding_streams
792    }
793
794    /// Returns the target difficulty limit for this network
795    pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
796        self.target_difficulty_limit
797    }
798
799    /// Returns true if proof-of-work validation should be disabled for this network
800    pub fn disable_pow(&self) -> bool {
801        self.disable_pow
802    }
803
804    /// Returns true if this network should allow transactions with transparent outputs
805    /// that spend coinbase outputs.
806    pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
807        self.should_allow_unshielded_coinbase_spends
808    }
809
810    /// Returns the pre-Blossom halving interval for this network
811    pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
812        self.pre_blossom_halving_interval
813    }
814
815    /// Returns the post-Blossom halving interval for this network
816    pub fn post_blossom_halving_interval(&self) -> HeightDiff {
817        self.post_blossom_halving_interval
818    }
819}
820
821impl Network {
822    /// Returns the parameters of this network if it is a Testnet.
823    pub fn parameters(&self) -> Option<Arc<Parameters>> {
824        if let Self::Testnet(parameters) = self {
825            Some(parameters.clone())
826        } else {
827            None
828        }
829    }
830
831    /// Returns true if proof-of-work validation should be disabled for this network
832    pub fn disable_pow(&self) -> bool {
833        if let Self::Testnet(params) = self {
834            params.disable_pow()
835        } else {
836            false
837        }
838    }
839
840    /// Returns slow start interval for this network
841    pub fn slow_start_interval(&self) -> Height {
842        if let Self::Testnet(params) = self {
843            params.slow_start_interval()
844        } else {
845            SLOW_START_INTERVAL
846        }
847    }
848
849    /// Returns slow start shift for this network
850    pub fn slow_start_shift(&self) -> Height {
851        if let Self::Testnet(params) = self {
852            params.slow_start_shift()
853        } else {
854            SLOW_START_SHIFT
855        }
856    }
857
858    /// Returns pre-NU6 funding streams for this network
859    ///
860    /// Commonly referred to as the "Dev Fund".
861    ///
862    /// Defined in [Zcash Protocol Specification §7.10.1][7.10.1]
863    ///
864    /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
865    pub fn pre_nu6_funding_streams(&self) -> &FundingStreams {
866        if let Self::Testnet(params) = self {
867            params.pre_nu6_funding_streams()
868        } else {
869            &PRE_NU6_FUNDING_STREAMS_MAINNET
870        }
871    }
872
873    /// Returns post-NU6 funding streams for this network
874    ///
875    /// Defined in [Zcash Protocol Specification §7.10.1][7.10.1]
876    ///
877    /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
878    pub fn post_nu6_funding_streams(&self) -> &FundingStreams {
879        if let Self::Testnet(params) = self {
880            params.post_nu6_funding_streams()
881        } else {
882            &POST_NU6_FUNDING_STREAMS_MAINNET
883        }
884    }
885
886    /// Returns post-Canopy funding streams for this network at the provided height
887    pub fn funding_streams(&self, height: Height) -> &FundingStreams {
888        if NetworkUpgrade::current(self, height) < NetworkUpgrade::Nu6 {
889            self.pre_nu6_funding_streams()
890        } else {
891            self.post_nu6_funding_streams()
892        }
893    }
894
895    /// Returns true if this network should allow transactions with transparent outputs
896    /// that spend coinbase outputs.
897    pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
898        if let Self::Testnet(params) = self {
899            params.should_allow_unshielded_coinbase_spends()
900        } else {
901            false
902        }
903    }
904}