Skip to main content

zebra_chain/parameters/network/
testnet.rs

1//! Types and implementation for Testnet consensus parameters
2
3use std::{collections::BTreeMap, fmt, sync::Arc};
4
5use crate::{
6    amount::{Amount, NonNegative},
7    block::{self, Height, HeightDiff},
8    parameters::{
9        checkpoint::list::{CheckpointList, TESTNET_CHECKPOINT_LIST},
10        constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT},
11        network::error::ParametersBuilderError,
12        network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
13        subsidy::{
14            constants::mainnet,
15            constants::testnet,
16            constants::{
17                BLOSSOM_POW_TARGET_SPACING_RATIO, FUNDING_STREAM_RECEIVER_DENOMINATOR,
18                POST_BLOSSOM_HALVING_INTERVAL, PRE_BLOSSOM_HALVING_INTERVAL,
19            },
20            funding_stream_address_period, FundingStreamReceiver, FundingStreamRecipient,
21            FundingStreams,
22        },
23        Network, NetworkKind, NetworkUpgrade,
24    },
25    transparent,
26    work::difficulty::{ExpandedDifficulty, U256},
27};
28
29use super::magic::Magic;
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    /// Creates a new [`ConfiguredFundingStreamRecipient`] with the provided receiver and default
73    /// values for other fields.
74    pub fn new_for(receiver: FundingStreamReceiver) -> Self {
75        use FundingStreamReceiver::*;
76        match receiver {
77            Ecc => Self {
78                receiver: Ecc,
79                numerator: 7,
80                addresses: Some(
81                    testnet::FUNDING_STREAM_ECC_ADDRESSES
82                        .map(ToString::to_string)
83                        .to_vec(),
84                ),
85            },
86            ZcashFoundation => Self {
87                receiver: ZcashFoundation,
88                numerator: 5,
89                addresses: Some(
90                    testnet::FUNDING_STREAM_ZF_ADDRESSES
91                        .map(ToString::to_string)
92                        .to_vec(),
93                ),
94            },
95            MajorGrants => Self {
96                receiver: MajorGrants,
97                numerator: 8,
98                addresses: Some(
99                    testnet::FUNDING_STREAM_MG_ADDRESSES
100                        .map(ToString::to_string)
101                        .to_vec(),
102                ),
103            },
104            Deferred => Self {
105                receiver,
106                numerator: 0,
107                addresses: None,
108            },
109        }
110    }
111
112    /// Converts a [`ConfiguredFundingStreamRecipient`] to a [`FundingStreamReceiver`] and [`FundingStreamRecipient`].
113    pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) {
114        (
115            self.receiver,
116            FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()),
117        )
118    }
119}
120
121/// Configurable one-time lockbox disbursement recipients for configured Testnets.
122#[derive(Serialize, Deserialize, Clone, Debug)]
123#[serde(deny_unknown_fields)]
124pub struct ConfiguredLockboxDisbursement {
125    /// The expected address for the lockbox disbursement output
126    pub address: String,
127    /// The expected disbursement amount
128    pub amount: Amount<NonNegative>,
129}
130
131/// Configurable funding streams for configured Testnets.
132#[derive(Serialize, Deserialize, Clone, Default, Debug)]
133#[serde(deny_unknown_fields)]
134pub struct ConfiguredFundingStreams {
135    /// Start and end height for funding streams see [`FundingStreams::height_range`] for more details.
136    pub height_range: Option<std::ops::Range<Height>>,
137    /// Funding stream recipients, see [`FundingStreams::recipients`] for more details.
138    pub recipients: Option<Vec<ConfiguredFundingStreamRecipient>>,
139}
140
141impl From<&FundingStreams> for ConfiguredFundingStreams {
142    fn from(value: &FundingStreams) -> Self {
143        Self {
144            height_range: Some(value.height_range().clone()),
145            recipients: Some(
146                value
147                    .recipients()
148                    .iter()
149                    .map(|(receiver, recipient)| ConfiguredFundingStreamRecipient {
150                        receiver: *receiver,
151                        numerator: recipient.numerator(),
152                        addresses: Some(
153                            recipient
154                                .addresses()
155                                .iter()
156                                .map(ToString::to_string)
157                                .collect(),
158                        ),
159                    })
160                    .collect(),
161            ),
162        }
163    }
164}
165
166impl From<(transparent::Address, Amount<NonNegative>)> for ConfiguredLockboxDisbursement {
167    fn from((address, amount): (transparent::Address, Amount<NonNegative>)) -> Self {
168        Self {
169            address: address.to_string(),
170            amount,
171        }
172    }
173}
174
175impl From<&BTreeMap<Height, NetworkUpgrade>> for ConfiguredActivationHeights {
176    fn from(activation_heights: &BTreeMap<Height, NetworkUpgrade>) -> Self {
177        let mut configured_activation_heights = ConfiguredActivationHeights::default();
178
179        for (height, network_upgrade) in activation_heights {
180            let field = match network_upgrade {
181                NetworkUpgrade::BeforeOverwinter => {
182                    &mut configured_activation_heights.before_overwinter
183                }
184                NetworkUpgrade::Overwinter => &mut configured_activation_heights.overwinter,
185                NetworkUpgrade::Sapling => &mut configured_activation_heights.sapling,
186                NetworkUpgrade::Blossom => &mut configured_activation_heights.blossom,
187                NetworkUpgrade::Heartwood => &mut configured_activation_heights.heartwood,
188                NetworkUpgrade::Canopy => &mut configured_activation_heights.canopy,
189                NetworkUpgrade::Nu5 => &mut configured_activation_heights.nu5,
190                NetworkUpgrade::Nu6 => &mut configured_activation_heights.nu6,
191                NetworkUpgrade::Nu6_1 => &mut configured_activation_heights.nu6_1,
192                NetworkUpgrade::Nu6_2 => &mut configured_activation_heights.nu6_2,
193                NetworkUpgrade::Nu6_3 => &mut configured_activation_heights.nu6_3,
194                NetworkUpgrade::Nu7 => &mut configured_activation_heights.nu7,
195                #[cfg(zcash_unstable = "zfuture")]
196                NetworkUpgrade::ZFuture => &mut configured_activation_heights.zfuture,
197                NetworkUpgrade::Genesis => continue,
198            };
199
200            *field = Some(height.0)
201        }
202
203        configured_activation_heights
204    }
205}
206
207impl From<BTreeMap<Height, NetworkUpgrade>> for ConfiguredActivationHeights {
208    fn from(value: BTreeMap<Height, NetworkUpgrade>) -> Self {
209        Self::from(&value)
210    }
211}
212
213impl ConfiguredFundingStreams {
214    /// Converts a [`ConfiguredFundingStreams`] to a [`FundingStreams`], using the provided default values
215    /// if `height_range` or `recipients` are None.
216    ///
217    /// # Panics
218    ///
219    /// If a default is required but was not passed
220    fn convert_with_default(
221        self,
222        default_funding_streams: Option<FundingStreams>,
223    ) -> FundingStreams {
224        let height_range = self.height_range.unwrap_or_else(|| {
225            default_funding_streams
226                .as_ref()
227                .expect("default required")
228                .height_range()
229                .clone()
230        });
231
232        let recipients = self
233            .recipients
234            .map(|recipients| {
235                recipients
236                    .into_iter()
237                    .map(ConfiguredFundingStreamRecipient::into_recipient)
238                    .collect()
239            })
240            .unwrap_or_else(|| {
241                default_funding_streams
242                    .as_ref()
243                    .expect("default required")
244                    .recipients()
245                    .clone()
246            });
247
248        assert!(
249            height_range.start <= height_range.end,
250            "funding stream end height must be above start height"
251        );
252
253        let funding_streams = FundingStreams::new(height_range.clone(), recipients);
254
255        // check that sum of receiver numerators is valid.
256
257        let sum_numerators: u64 = funding_streams
258            .recipients()
259            .values()
260            .map(|r| r.numerator())
261            .sum();
262
263        assert!(
264            sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR,
265            "sum of funding stream numerators must not be \
266         greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}"
267        );
268
269        funding_streams
270    }
271
272    /// Converts the [`ConfiguredFundingStreams`] to a [`FundingStreams`].
273    ///
274    /// # Panics
275    ///
276    /// If `height_range` is None.
277    pub fn into_funding_streams_unchecked(self) -> FundingStreams {
278        let height_range = self.height_range.expect("must have height range");
279        let recipients = self
280            .recipients
281            .into_iter()
282            .flat_map(|recipients| {
283                recipients
284                    .into_iter()
285                    .map(ConfiguredFundingStreamRecipient::into_recipient)
286            })
287            .collect();
288
289        FundingStreams::new(height_range, recipients)
290    }
291}
292
293/// Returns the number of funding stream address periods there are for the provided network and height range.
294fn num_funding_stream_addresses_required_for_height_range(
295    height_range: &std::ops::Range<Height>,
296    network: &Network,
297) -> usize {
298    1u32.checked_add(funding_stream_address_period(
299        height_range
300            .end
301            .previous()
302            .expect("end height must be above start height and genesis height"),
303        network,
304    ))
305    .expect("no overflow should happen in this sum")
306    .checked_sub(funding_stream_address_period(height_range.start, network))
307    .expect("no overflow should happen in this sub") as usize
308}
309
310/// Checks that the provided [`FundingStreams`] has sufficient recipient addresses for the
311/// funding stream address period of the provided [`Network`].
312fn check_funding_stream_address_period(funding_streams: &FundingStreams, network: &Network) {
313    let expected_min_num_addresses = num_funding_stream_addresses_required_for_height_range(
314        funding_streams.height_range(),
315        network,
316    );
317
318    for (&receiver, recipient) in funding_streams.recipients() {
319        if receiver == FundingStreamReceiver::Deferred {
320            // The `Deferred` receiver doesn't need any addresses.
321            continue;
322        }
323
324        let num_addresses = recipient.addresses().len();
325        assert!(
326            num_addresses >= expected_min_num_addresses,
327            "recipients must have a sufficient number of addresses for height range, \
328             minimum num addresses required: {expected_min_num_addresses}, only {num_addresses} were provided.\
329             receiver: {receiver:?}, recipient: {recipient:?}"
330        );
331
332        for address in recipient.addresses() {
333            assert_eq!(
334                address.network_kind(),
335                NetworkKind::Testnet,
336                "configured funding stream addresses must be for Testnet"
337            );
338        }
339    }
340}
341
342/// Configurable activation heights for Regtest and configured Testnets.
343#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq)]
344#[serde(rename_all = "PascalCase", deny_unknown_fields)]
345pub struct ConfiguredActivationHeights {
346    /// Activation height for `BeforeOverwinter` network upgrade.
347    pub before_overwinter: Option<u32>,
348    /// Activation height for `Overwinter` network upgrade.
349    pub overwinter: Option<u32>,
350    /// Activation height for `Sapling` network upgrade.
351    pub sapling: Option<u32>,
352    /// Activation height for `Blossom` network upgrade.
353    pub blossom: Option<u32>,
354    /// Activation height for `Heartwood` network upgrade.
355    pub heartwood: Option<u32>,
356    /// Activation height for `Canopy` network upgrade.
357    pub canopy: Option<u32>,
358    /// Activation height for `NU5` network upgrade.
359    #[serde(rename = "NU5")]
360    pub nu5: Option<u32>,
361    /// Activation height for `NU6` network upgrade.
362    #[serde(rename = "NU6")]
363    pub nu6: Option<u32>,
364    /// Activation height for `NU6.1` network upgrade.
365    #[serde(rename = "NU6.1")]
366    pub nu6_1: Option<u32>,
367    /// Activation height for `NU6.2` network upgrade.
368    #[serde(rename = "NU6.2")]
369    pub nu6_2: Option<u32>,
370    /// Activation height for `NU6.3` (Ironwood) network upgrade.
371    #[serde(rename = "NU6.3")]
372    pub nu6_3: Option<u32>,
373    /// Activation height for `NU7` network upgrade.
374    #[serde(rename = "NU7")]
375    pub nu7: Option<u32>,
376    /// Activation height for `ZFuture` network upgrade.
377    #[serde(rename = "ZFuture")]
378    #[cfg(zcash_unstable = "zfuture")]
379    pub zfuture: Option<u32>,
380}
381
382impl ConfiguredActivationHeights {
383    /// Converts a [`ConfiguredActivationHeights`] to one that uses the default values for Regtest where
384    /// no activation heights are specified.
385    fn for_regtest(self) -> Self {
386        let Self {
387            before_overwinter,
388            overwinter,
389            sapling,
390            blossom,
391            heartwood,
392            canopy,
393            nu5,
394            nu6,
395            nu6_1,
396            nu6_2,
397            nu6_3,
398            nu7,
399            #[cfg(zcash_unstable = "zfuture")]
400            zfuture,
401        } = self;
402
403        let overwinter = overwinter.or(before_overwinter).or(Some(1));
404        let sapling = sapling.or(overwinter);
405        let blossom = blossom.or(sapling);
406        let heartwood = heartwood.or(blossom);
407        let canopy = canopy.or(heartwood);
408
409        Self {
410            before_overwinter,
411            overwinter,
412            sapling,
413            blossom,
414            heartwood,
415            canopy,
416            nu5,
417            nu6,
418            nu6_1,
419            nu6_2,
420            nu6_3,
421            nu7,
422            #[cfg(zcash_unstable = "zfuture")]
423            zfuture,
424        }
425    }
426}
427
428/// Configurable checkpoints, either a path to a checkpoints file, a "default" keyword to indicate
429/// that Zebra should use the default Testnet checkpoints, or a list of block heights and hashes.
430#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
431#[serde(untagged)]
432pub enum ConfiguredCheckpoints {
433    /// A boolean indicating whether Zebra should use the default Testnet checkpoints.
434    Default(bool),
435    /// A path to a checkpoints file to be used as Zebra's checkpoints.
436    Path(std::path::PathBuf),
437    /// Directly configured block heights and hashes to be used as Zebra's checkpoints.
438    HeightsAndHashes(Vec<(block::Height, block::Hash)>),
439}
440
441impl Default for ConfiguredCheckpoints {
442    fn default() -> Self {
443        Self::Default(false)
444    }
445}
446
447impl From<Arc<CheckpointList>> for ConfiguredCheckpoints {
448    fn from(value: Arc<CheckpointList>) -> Self {
449        Self::HeightsAndHashes(value.iter_cloned().collect())
450    }
451}
452
453impl From<bool> for ConfiguredCheckpoints {
454    fn from(value: bool) -> Self {
455        Self::Default(value)
456    }
457}
458
459/// Builder for the [`Parameters`] struct.
460#[derive(Clone, Debug, Eq, PartialEq)]
461pub struct ParametersBuilder {
462    /// The name of this network to be used by the `Display` trait impl.
463    network_name: String,
464    /// The network magic, acts as an identifier for the network.
465    network_magic: Magic,
466    /// The genesis block hash
467    genesis_hash: block::Hash,
468    /// The network upgrade activation heights for this network, see [`Parameters::activation_heights`] for more details.
469    activation_heights: BTreeMap<Height, NetworkUpgrade>,
470    /// Slow start interval for this network
471    slow_start_interval: Height,
472    /// Funding streams for this network
473    funding_streams: Vec<FundingStreams>,
474    /// A flag indicating whether to allow changes to fields that affect
475    /// the funding stream address period.
476    should_lock_funding_stream_address_period: bool,
477    /// Target difficulty limit for this network
478    target_difficulty_limit: ExpandedDifficulty,
479    /// A flag for disabling proof-of-work checks when Zebra is validating blocks
480    disable_pow: bool,
481    /// Whether to allow transactions with transparent outputs to spend coinbase outputs,
482    /// similar to `fCoinbaseMustBeShielded` in zcashd.
483    should_allow_unshielded_coinbase_spends: bool,
484    /// The pre-Blossom halving interval for this network
485    pre_blossom_halving_interval: HeightDiff,
486    /// The post-Blossom halving interval for this network
487    post_blossom_halving_interval: HeightDiff,
488    /// Expected one-time lockbox disbursement outputs in NU6.1 activation block coinbase for this network
489    lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
490    /// Checkpointed block hashes and heights for this network.
491    checkpoints: Arc<CheckpointList>,
492    /// Height at which the soft-fork to temporarily disable Orchard in transactions activates
493    temporary_orchard_disabling_soft_fork_height: Option<Height>,
494}
495
496impl Default for ParametersBuilder {
497    /// Creates a [`ParametersBuilder`] with all of the default Testnet parameters except `network_name`.
498    fn default() -> Self {
499        Self {
500            network_name: "UnknownTestnet".to_string(),
501            network_magic: magics::TESTNET,
502            // # Correctness
503            //
504            // `Genesis` network upgrade activation height must always be 0
505            activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
506            genesis_hash: TESTNET_GENESIS_HASH
507                .parse()
508                .expect("hard-coded hash parses"),
509            slow_start_interval: SLOW_START_INTERVAL,
510            // Testnet PoWLimit is defined as `2^251 - 1` on page 73 of the protocol specification:
511            // <https://zips.z.cash/protocol/protocol.pdf>
512            //
513            // The PoWLimit must be converted into a compact representation before using it
514            // to perform difficulty filter checks (see https://github.com/zcash/zips/pull/417).
515            target_difficulty_limit: ExpandedDifficulty::from((U256::one() << 251) - 1)
516                .to_compact()
517                .to_expanded()
518                .expect("difficulty limits are valid expanded values"),
519            disable_pow: false,
520            funding_streams: testnet::FUNDING_STREAMS.clone(),
521            should_lock_funding_stream_address_period: false,
522            pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL,
523            post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL,
524            should_allow_unshielded_coinbase_spends: false,
525            lockbox_disbursements: testnet::NU6_1_LOCKBOX_DISBURSEMENTS
526                .iter()
527                .map(|(addr, amount)| (addr.to_string(), *amount))
528                .collect(),
529            checkpoints: TESTNET_CHECKPOINT_LIST.clone(),
530            temporary_orchard_disabling_soft_fork_height: Some(
531                super::TESTNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT,
532            ),
533        }
534    }
535}
536
537impl ParametersBuilder {
538    /// Sets the network name to be used in the [`Parameters`] being built.
539    pub fn with_network_name(
540        mut self,
541        network_name: impl fmt::Display,
542    ) -> Result<Self, ParametersBuilderError> {
543        let network_name = network_name.to_string();
544
545        if RESERVED_NETWORK_NAMES.contains(&network_name.as_str()) {
546            return Err(ParametersBuilderError::ReservedNetworkName {
547                network_name,
548                reserved_names: RESERVED_NETWORK_NAMES.to_vec(),
549            });
550        }
551
552        if network_name.len() > MAX_NETWORK_NAME_LENGTH {
553            return Err(ParametersBuilderError::NetworkNameTooLong {
554                network_name,
555                max_length: MAX_NETWORK_NAME_LENGTH,
556            });
557        }
558
559        if !network_name
560            .chars()
561            .all(|x| x.is_alphanumeric() || x == '_')
562        {
563            return Err(ParametersBuilderError::InvalidCharacter);
564        }
565
566        self.network_name = network_name;
567        Ok(self)
568    }
569
570    /// Sets the network name to be used in the [`Parameters`] being built.
571    pub fn with_network_magic(
572        mut self,
573        network_magic: Magic,
574    ) -> Result<Self, ParametersBuilderError> {
575        if [magics::MAINNET, magics::REGTEST]
576            .into_iter()
577            .any(|reserved_magic| network_magic == reserved_magic)
578        {
579            return Err(ParametersBuilderError::ReservedNetworkMagic);
580        }
581
582        self.network_magic = network_magic;
583
584        Ok(self)
585    }
586
587    /// Parses the hex-encoded block hash and sets it as the genesis hash in the [`Parameters`] being built.
588    pub fn with_genesis_hash(
589        mut self,
590        genesis_hash: impl fmt::Display,
591    ) -> Result<Self, ParametersBuilderError> {
592        self.genesis_hash = genesis_hash
593            .to_string()
594            .parse()
595            .map_err(|_| ParametersBuilderError::InvalidGenesisHash)?;
596        Ok(self)
597    }
598
599    /// Checks that the provided network upgrade activation heights are in the correct order, then
600    /// sets them as the new network upgrade activation heights.
601    pub fn with_activation_heights(
602        mut self,
603        ConfiguredActivationHeights {
604            before_overwinter,
605            overwinter,
606            sapling,
607            blossom,
608            heartwood,
609            canopy,
610            nu5,
611            nu6,
612            nu6_1,
613            nu6_2,
614            nu6_3,
615            nu7,
616            #[cfg(zcash_unstable = "zfuture")]
617            zfuture,
618        }: ConfiguredActivationHeights,
619    ) -> Result<Self, ParametersBuilderError> {
620        use NetworkUpgrade::*;
621
622        if self.should_lock_funding_stream_address_period {
623            return Err(ParametersBuilderError::LockFundingStreams);
624        }
625
626        // # Correctness
627        //
628        // These must be in order so that later network upgrades overwrite prior ones
629        // if multiple network upgrades are configured with the same activation height.
630        let activation_heights: BTreeMap<_, _> = {
631            let activation_heights = before_overwinter
632                .into_iter()
633                .map(|h| (h, BeforeOverwinter))
634                .chain(overwinter.into_iter().map(|h| (h, Overwinter)))
635                .chain(sapling.into_iter().map(|h| (h, Sapling)))
636                .chain(blossom.into_iter().map(|h| (h, Blossom)))
637                .chain(heartwood.into_iter().map(|h| (h, Heartwood)))
638                .chain(canopy.into_iter().map(|h| (h, Canopy)))
639                .chain(nu5.into_iter().map(|h| (h, Nu5)))
640                .chain(nu6.into_iter().map(|h| (h, Nu6)))
641                .chain(nu6_1.into_iter().map(|h| (h, Nu6_1)))
642                .chain(nu6_2.into_iter().map(|h| (h, Nu6_2)))
643                .chain(nu6_3.into_iter().map(|h| (h, Nu6_3)))
644                .chain(nu7.into_iter().map(|h| (h, Nu7)));
645
646            #[cfg(zcash_unstable = "zfuture")]
647            let activation_heights =
648                activation_heights.chain(zfuture.into_iter().map(|h| (h, ZFuture)));
649
650            activation_heights
651                .map(|(h, nu)| {
652                    let height = h
653                        .try_into()
654                        .map_err(|_| ParametersBuilderError::InvalidActivationHeight)?;
655                    Ok((height, nu))
656                })
657                .collect::<Result<BTreeMap<_, _>, _>>()?
658        };
659
660        let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect();
661
662        // Check that the provided network upgrade activation heights are in the same order by height as the default testnet activation heights
663        let mut activation_heights_iter = activation_heights.iter();
664        for expected_network_upgrade in NetworkUpgrade::iter() {
665            if !network_upgrades.contains(&expected_network_upgrade) {
666                continue;
667            } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
668                if height == Height(0) {
669                    return Err(ParametersBuilderError::InvalidHeightZero);
670                }
671
672                if network_upgrade != expected_network_upgrade {
673                    return Err(ParametersBuilderError::OutOfOrderUpgrades);
674                }
675            }
676        }
677
678        // # Correctness
679        //
680        // Height(0) must be reserved for the `NetworkUpgrade::Genesis`.
681        self.activation_heights.split_off(&Height(1));
682        self.activation_heights.extend(activation_heights);
683
684        Ok(self)
685    }
686
687    /// Sets the slow start interval to be used in the [`Parameters`] being built.
688    pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
689        self.slow_start_interval = slow_start_interval;
690        self
691    }
692
693    /// Sets funding streams to be used in the [`Parameters`] being built.
694    ///
695    /// # Panics
696    ///
697    /// If `funding_streams` is longer than `testnet::FUNDING_STREAMS`, and one
698    /// of the extra streams requires a default value.
699    pub fn with_funding_streams(mut self, funding_streams: Vec<ConfiguredFundingStreams>) -> Self {
700        self.funding_streams = funding_streams
701            .into_iter()
702            .enumerate()
703            .map(|(idx, streams)| {
704                let default_streams = testnet::FUNDING_STREAMS.get(idx).cloned();
705                streams.convert_with_default(default_streams)
706            })
707            .collect();
708        self.should_lock_funding_stream_address_period = true;
709        self
710    }
711
712    /// Clears funding streams from the [`Parameters`] being built.
713    pub fn clear_funding_streams(mut self) -> Self {
714        self.funding_streams = vec![];
715        self
716    }
717
718    /// Extends the configured funding streams to have as many recipients as are required for their
719    /// height ranges by repeating the recipients that have been configured.
720    ///
721    /// This should be called after configuring the desired network upgrade activation heights.
722    pub fn extend_funding_streams(mut self) -> Self {
723        let network = self.to_network_unchecked();
724
725        for funding_streams in &mut self.funding_streams {
726            funding_streams.extend_recipient_addresses(
727                num_funding_stream_addresses_required_for_height_range(
728                    funding_streams.height_range(),
729                    &network,
730                ),
731            );
732        }
733
734        self
735    }
736
737    /// Sets the target difficulty limit to be used in the [`Parameters`] being built.
738    // TODO: Accept a hex-encoded String instead?
739    pub fn with_target_difficulty_limit(
740        mut self,
741        target_difficulty_limit: impl Into<ExpandedDifficulty>,
742    ) -> Result<Self, ParametersBuilderError> {
743        self.target_difficulty_limit = target_difficulty_limit
744            .into()
745            .to_compact()
746            .to_expanded()
747            .ok_or(ParametersBuilderError::InvaildDifficultyLimits)?;
748        Ok(self)
749    }
750
751    /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
752    pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
753        self.disable_pow = disable_pow;
754        self
755    }
756
757    /// Sets whether coinbase outputs may be spent into transparent outputs in the
758    /// [`Parameters`] being built (the inverse of zcashd's `-regtestshieldcoinbase`).
759    pub fn with_unshielded_coinbase_spends(
760        mut self,
761        should_allow_unshielded_coinbase_spends: bool,
762    ) -> Self {
763        self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends;
764        self
765    }
766
767    /// Sets the pre and post Blosssom halving intervals to be used in the [`Parameters`] being built.
768    pub fn with_halving_interval(
769        mut self,
770        pre_blossom_halving_interval: HeightDiff,
771    ) -> Result<Self, ParametersBuilderError> {
772        if self.should_lock_funding_stream_address_period {
773            return Err(ParametersBuilderError::HalvingIntervalAfterFundingStreams);
774        }
775
776        self.pre_blossom_halving_interval = pre_blossom_halving_interval;
777        self.post_blossom_halving_interval =
778            self.pre_blossom_halving_interval * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
779        Ok(self)
780    }
781
782    /// Sets the expected one-time lockbox disbursement outputs for this network
783    pub fn with_lockbox_disbursements(
784        mut self,
785        lockbox_disbursements: Vec<ConfiguredLockboxDisbursement>,
786    ) -> Self {
787        self.lockbox_disbursements = lockbox_disbursements
788            .into_iter()
789            .map(|ConfiguredLockboxDisbursement { address, amount }| (address, amount))
790            .collect();
791        self
792    }
793
794    /// Sets the checkpoints for the network as the provided [`ConfiguredCheckpoints`].
795    pub fn with_checkpoints(
796        mut self,
797        checkpoints: impl Into<ConfiguredCheckpoints>,
798    ) -> Result<Self, ParametersBuilderError> {
799        self.checkpoints = match checkpoints.into() {
800            ConfiguredCheckpoints::Default(true) => TESTNET_CHECKPOINT_LIST.clone(),
801            ConfiguredCheckpoints::Default(false) => Arc::new(
802                CheckpointList::from_list([(block::Height(0), self.genesis_hash)])
803                    .map_err(|_| ParametersBuilderError::FailedToParseDefaultCheckpoint)?,
804            ),
805            ConfiguredCheckpoints::Path(path_buf) => {
806                let Ok(raw_checkpoints_str) = std::fs::read_to_string(&path_buf) else {
807                    return Err(ParametersBuilderError::FailedToReadCheckpointFile {
808                        path_buf: path_buf.clone(),
809                    });
810                };
811
812                Arc::new(
813                    raw_checkpoints_str
814                        .parse::<CheckpointList>()
815                        .map_err(|err| ParametersBuilderError::FailedToParseCheckpointFile {
816                            path_buf: path_buf.clone(),
817                            err: err.to_string(),
818                        })?,
819                )
820            }
821            ConfiguredCheckpoints::HeightsAndHashes(items) => Arc::new(
822                CheckpointList::from_list(items)
823                    .map_err(|_| ParametersBuilderError::InvalidCustomCheckpoints)?,
824            ),
825        };
826
827        Ok(self)
828    }
829
830    /// Clears checkpoints from the [`Parameters`] being built, keeping the genesis checkpoint.
831    pub fn clear_checkpoints(self) -> Result<Self, ParametersBuilderError> {
832        self.with_checkpoints(ConfiguredCheckpoints::Default(false))
833    }
834
835    /// Sets the height for this network at which the soft fork that temporarily disables
836    /// Orchard transactions will activate.
837    pub fn with_temporary_orchard_disabling_soft_fork_height(mut self, height: Height) -> Self {
838        self.temporary_orchard_disabling_soft_fork_height = Some(height);
839        self
840    }
841
842    /// Disables the soft fork that would temporarily disable Orchard transactions.
843    pub fn disable_temporary_orchard_disabling_soft_fork(mut self) -> Self {
844        self.temporary_orchard_disabling_soft_fork_height = None;
845        self
846    }
847
848    /// Converts the builder to a [`Parameters`] struct
849    fn finish(self) -> Parameters {
850        let Self {
851            network_name,
852            network_magic,
853            genesis_hash,
854            activation_heights,
855            slow_start_interval,
856            funding_streams,
857            should_lock_funding_stream_address_period: _,
858            target_difficulty_limit,
859            disable_pow,
860            should_allow_unshielded_coinbase_spends,
861            pre_blossom_halving_interval,
862            post_blossom_halving_interval,
863            lockbox_disbursements,
864            checkpoints,
865            temporary_orchard_disabling_soft_fork_height,
866        } = self;
867        Parameters {
868            network_name,
869            network_magic,
870            genesis_hash,
871            activation_heights,
872            slow_start_interval,
873            slow_start_shift: Height(slow_start_interval.0 / 2),
874            funding_streams,
875            target_difficulty_limit,
876            disable_pow,
877            should_allow_unshielded_coinbase_spends,
878            pre_blossom_halving_interval,
879            post_blossom_halving_interval,
880            lockbox_disbursements,
881            checkpoints,
882            temporary_orchard_disabling_soft_fork_height,
883        }
884    }
885
886    /// Converts the builder to a configured [`Network::Testnet`]
887    fn to_network_unchecked(&self) -> Network {
888        Network::new_configured_testnet(self.clone().finish())
889    }
890
891    /// Checks funding streams and converts the builder to a configured [`Network::Testnet`]
892    pub fn to_network(self) -> Result<Network, ParametersBuilderError> {
893        let network = self.to_network_unchecked();
894
895        // Final check that the configured funding streams will be valid for these Testnet parameters.
896        for fs in &self.funding_streams {
897            // Check that the funding streams are valid for the configured Testnet parameters.
898            check_funding_stream_address_period(fs, &network);
899        }
900
901        // Final check that the configured checkpoints are valid for this network.
902        if network.checkpoint_list().hash(Height(0)) != Some(network.genesis_hash()) {
903            return Err(ParametersBuilderError::CheckpointGenesisMismatch);
904        }
905        if network.checkpoint_list().max_height() < network.mandatory_checkpoint_height() {
906            return Err(ParametersBuilderError::InsufficientCheckpointCoverage);
907        }
908
909        Ok(network)
910    }
911
912    /// Returns true if these [`Parameters`] should be compatible with the default Testnet parameters.
913    pub fn is_compatible_with_default_parameters(&self) -> bool {
914        let Self {
915            network_name: _,
916            network_magic,
917            genesis_hash,
918            activation_heights,
919            slow_start_interval,
920            funding_streams,
921            should_lock_funding_stream_address_period: _,
922            target_difficulty_limit,
923            disable_pow,
924            should_allow_unshielded_coinbase_spends,
925            pre_blossom_halving_interval,
926            post_blossom_halving_interval,
927            lockbox_disbursements,
928            checkpoints: _,
929            temporary_orchard_disabling_soft_fork_height: _,
930        } = Self::default();
931
932        self.activation_heights == activation_heights
933            && self.network_magic == network_magic
934            && self.genesis_hash == genesis_hash
935            && self.slow_start_interval == slow_start_interval
936            && self.funding_streams == funding_streams
937            && self.target_difficulty_limit == target_difficulty_limit
938            && self.disable_pow == disable_pow
939            && self.should_allow_unshielded_coinbase_spends
940                == should_allow_unshielded_coinbase_spends
941            && self.pre_blossom_halving_interval == pre_blossom_halving_interval
942            && self.post_blossom_halving_interval == post_blossom_halving_interval
943            && self.lockbox_disbursements == lockbox_disbursements
944    }
945}
946
947/// A struct of parameters for configuring Regtest in Zebra.
948#[derive(Debug, Default, Clone)]
949pub struct RegtestParameters {
950    /// The configured network upgrade activation heights to use on Regtest
951    pub activation_heights: ConfiguredActivationHeights,
952    /// Configured funding streams
953    pub funding_streams: Option<Vec<ConfiguredFundingStreams>>,
954    /// Expected one-time lockbox disbursement outputs in NU6.1 activation block coinbase for Regtest
955    pub lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
956    /// Configured checkpointed block heights and hashes.
957    pub checkpoints: Option<ConfiguredCheckpoints>,
958    /// Whether funding stream addresses should be repeated to fill all required funding stream periods.
959    pub extend_funding_stream_addresses_as_required: Option<bool>,
960    /// Whether to allow coinbase spends to have transparent outputs (inverse of
961    /// zcashd's `-regtestshieldcoinbase`).
962    pub should_allow_unshielded_coinbase_spends: Option<bool>,
963}
964
965impl From<ConfiguredActivationHeights> for RegtestParameters {
966    fn from(value: ConfiguredActivationHeights) -> Self {
967        Self {
968            activation_heights: value,
969            ..Default::default()
970        }
971    }
972}
973
974/// Network consensus parameters for test networks such as Regtest and the default Testnet.
975#[derive(Clone, Debug, Eq, PartialEq)]
976pub struct Parameters {
977    /// The name of this network to be used by the `Display` trait impl.
978    network_name: String,
979    /// The network magic, acts as an identifier for the network.
980    network_magic: Magic,
981    /// The genesis block hash
982    genesis_hash: block::Hash,
983    /// The network upgrade activation heights for this network.
984    activation_heights: BTreeMap<Height, NetworkUpgrade>,
985    /// Slow start interval for this network
986    slow_start_interval: Height,
987    /// Slow start shift for this network, always half the slow start interval
988    slow_start_shift: Height,
989    /// Funding streams for this network
990    funding_streams: Vec<FundingStreams>,
991    /// Target difficulty limit for this network
992    target_difficulty_limit: ExpandedDifficulty,
993    /// A flag for disabling proof-of-work checks when Zebra is validating blocks
994    disable_pow: bool,
995    /// Whether to allow transactions with transparent outputs to spend coinbase outputs,
996    /// similar to `fCoinbaseMustBeShielded` in zcashd.
997    should_allow_unshielded_coinbase_spends: bool,
998    /// Pre-Blossom halving interval for this network
999    pre_blossom_halving_interval: HeightDiff,
1000    /// Post-Blossom halving interval for this network
1001    post_blossom_halving_interval: HeightDiff,
1002    /// Expected one-time lockbox disbursement outputs in NU6.1 activation block coinbase for this network
1003    lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
1004    /// List of checkpointed block heights and hashes
1005    checkpoints: Arc<CheckpointList>,
1006    /// Height at which the soft-fork to temporarily disable Orchard in transactions activates
1007    temporary_orchard_disabling_soft_fork_height: Option<Height>,
1008}
1009
1010impl Default for Parameters {
1011    /// Returns an instance of the default public testnet [`Parameters`].
1012    fn default() -> Self {
1013        Self {
1014            network_name: "Testnet".to_string(),
1015            ..Self::build().finish()
1016        }
1017    }
1018}
1019
1020impl Parameters {
1021    /// Creates a new [`ParametersBuilder`].
1022    pub fn build() -> ParametersBuilder {
1023        ParametersBuilder::default()
1024    }
1025
1026    /// Accepts a [`ConfiguredActivationHeights`].
1027    ///
1028    /// Creates an instance of [`Parameters`] with `Regtest` values.
1029    pub fn new_regtest(
1030        RegtestParameters {
1031            activation_heights,
1032            funding_streams,
1033            lockbox_disbursements,
1034            checkpoints,
1035            extend_funding_stream_addresses_as_required,
1036            should_allow_unshielded_coinbase_spends,
1037        }: RegtestParameters,
1038    ) -> Result<Self, ParametersBuilderError> {
1039        let mut parameters = Self::build()
1040            .with_genesis_hash(REGTEST_GENESIS_HASH)?
1041            // This value is chosen to match zcashd, see: <https://github.com/zcash/zcash/blob/master/src/chainparams.cpp#L654>
1042            .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))?
1043            .with_disable_pow(true)
1044            .with_unshielded_coinbase_spends(
1045                should_allow_unshielded_coinbase_spends.unwrap_or(true),
1046            )
1047            .with_slow_start_interval(Height::MIN)
1048            // Like the default Testnet activation heights stripped below, the default Testnet's
1049            // temporary Orchard-disabling soft fork does not apply to Regtest.
1050            .disable_temporary_orchard_disabling_soft_fork()
1051            // Removes default Testnet activation heights if not configured,
1052            // most network upgrades are disabled by default for Regtest in zcashd
1053            .with_activation_heights(activation_heights.for_regtest())?
1054            .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL)?
1055            .with_funding_streams(funding_streams.unwrap_or_default())
1056            .with_lockbox_disbursements(lockbox_disbursements.unwrap_or_default())
1057            .with_checkpoints(checkpoints.unwrap_or_default())?;
1058
1059        if Some(true) == extend_funding_stream_addresses_as_required {
1060            parameters = parameters.extend_funding_streams();
1061        }
1062
1063        Ok(Self {
1064            network_name: "Regtest".to_string(),
1065            network_magic: magics::REGTEST,
1066            ..parameters.finish()
1067        })
1068    }
1069
1070    /// Returns true if the instance of [`Parameters`] represents the default public Testnet.
1071    pub fn is_default_testnet(&self) -> bool {
1072        self == &Self::default()
1073    }
1074
1075    /// Returns true if the instance of [`Parameters`] represents Regtest.
1076    pub fn is_regtest(&self) -> bool {
1077        if self.network_magic != magics::REGTEST {
1078            return false;
1079        }
1080
1081        let Self {
1082            network_name,
1083            // Already checked network magic above
1084            network_magic: _,
1085            genesis_hash,
1086            // Activation heights are configurable on Regtest
1087            activation_heights: _,
1088            slow_start_interval,
1089            slow_start_shift,
1090            funding_streams: _,
1091            target_difficulty_limit,
1092            disable_pow,
1093            // Configurable on Regtest
1094            should_allow_unshielded_coinbase_spends: _,
1095            pre_blossom_halving_interval,
1096            post_blossom_halving_interval,
1097            lockbox_disbursements: _,
1098            checkpoints: _,
1099            temporary_orchard_disabling_soft_fork_height: _,
1100        } = Self::new_regtest(Default::default()).expect("default regtest parameters are valid");
1101
1102        self.network_name == network_name
1103            && self.genesis_hash == genesis_hash
1104            && self.slow_start_interval == slow_start_interval
1105            && self.slow_start_shift == slow_start_shift
1106            && self.target_difficulty_limit == target_difficulty_limit
1107            && self.disable_pow == disable_pow
1108            && self.pre_blossom_halving_interval == pre_blossom_halving_interval
1109            && self.post_blossom_halving_interval == post_blossom_halving_interval
1110    }
1111
1112    /// Returns the network name
1113    pub fn network_name(&self) -> &str {
1114        &self.network_name
1115    }
1116
1117    /// Returns the network magic
1118    pub fn network_magic(&self) -> Magic {
1119        self.network_magic
1120    }
1121
1122    /// Returns the genesis hash
1123    pub fn genesis_hash(&self) -> block::Hash {
1124        self.genesis_hash
1125    }
1126
1127    /// Returns the network upgrade activation heights
1128    pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
1129        &self.activation_heights
1130    }
1131
1132    /// Returns slow start interval for this network
1133    pub fn slow_start_interval(&self) -> Height {
1134        self.slow_start_interval
1135    }
1136
1137    /// Returns slow start shift for this network
1138    pub fn slow_start_shift(&self) -> Height {
1139        self.slow_start_shift
1140    }
1141
1142    /// Returns funding streams for this network.
1143    pub fn funding_streams(&self) -> &Vec<FundingStreams> {
1144        &self.funding_streams
1145    }
1146
1147    /// Returns the target difficulty limit for this network
1148    pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
1149        self.target_difficulty_limit
1150    }
1151
1152    /// Returns true if proof-of-work validation should be disabled for this network
1153    pub fn disable_pow(&self) -> bool {
1154        self.disable_pow
1155    }
1156
1157    /// Returns true if this network should allow transactions with transparent outputs
1158    /// that spend coinbase outputs.
1159    pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1160        self.should_allow_unshielded_coinbase_spends
1161    }
1162
1163    /// Returns the pre-Blossom halving interval for this network
1164    pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
1165        self.pre_blossom_halving_interval
1166    }
1167
1168    /// Returns the post-Blossom halving interval for this network
1169    pub fn post_blossom_halving_interval(&self) -> HeightDiff {
1170        self.post_blossom_halving_interval
1171    }
1172
1173    /// Returns the expected total value of the sum of all NU6.1 one-time lockbox disbursement output values for this network.
1174    pub fn lockbox_disbursement_total_amount(&self) -> Amount<NonNegative> {
1175        self.lockbox_disbursements()
1176            .into_iter()
1177            .map(|(_addr, amount)| amount)
1178            .reduce(|a, b| (a + b).expect("sum of configured amounts should be valid"))
1179            .unwrap_or_default()
1180    }
1181
1182    /// Returns the expected NU6.1 lockbox disbursement outputs for this network.
1183    pub fn lockbox_disbursements(&self) -> Vec<(transparent::Address, Amount<NonNegative>)> {
1184        self.lockbox_disbursements
1185            .iter()
1186            .map(|(addr, amount)| {
1187                (
1188                    addr.parse().expect("hard-coded address must deserialize"),
1189                    *amount,
1190                )
1191            })
1192            .collect()
1193    }
1194
1195    /// Returns the checkpoints for this network.
1196    pub fn checkpoints(&self) -> Arc<CheckpointList> {
1197        self.checkpoints.clone()
1198    }
1199
1200    /// Returns the height at which the soft-fork to temporarily disable Orchard in
1201    /// transactions activates.
1202    pub fn temporary_orchard_disabling_soft_fork_height(&self) -> Option<Height> {
1203        self.temporary_orchard_disabling_soft_fork_height
1204    }
1205}
1206
1207impl Network {
1208    /// Returns the parameters of this network if it is a Testnet.
1209    pub fn parameters(&self) -> Option<Arc<Parameters>> {
1210        if let Self::Testnet(parameters) = self {
1211            Some(parameters.clone())
1212        } else {
1213            None
1214        }
1215    }
1216
1217    /// Returns true if proof-of-work validation should be disabled for this network
1218    pub fn disable_pow(&self) -> bool {
1219        if let Self::Testnet(params) = self {
1220            params.disable_pow()
1221        } else {
1222            false
1223        }
1224    }
1225
1226    /// Returns slow start interval for this network
1227    pub fn slow_start_interval(&self) -> Height {
1228        if let Self::Testnet(params) = self {
1229            params.slow_start_interval()
1230        } else {
1231            SLOW_START_INTERVAL
1232        }
1233    }
1234
1235    /// Returns slow start shift for this network
1236    pub fn slow_start_shift(&self) -> Height {
1237        if let Self::Testnet(params) = self {
1238            params.slow_start_shift()
1239        } else {
1240            SLOW_START_SHIFT
1241        }
1242    }
1243
1244    /// Returns post-Canopy funding streams for this network at the provided height
1245    pub fn funding_streams(&self, height: Height) -> Option<&FundingStreams> {
1246        self.all_funding_streams()
1247            .iter()
1248            .find(|&streams| streams.height_range().contains(&height))
1249    }
1250
1251    /// Returns post-Canopy funding streams for this network at the provided height
1252    pub fn all_funding_streams(&self) -> &Vec<FundingStreams> {
1253        if let Self::Testnet(params) = self {
1254            params.funding_streams()
1255        } else {
1256            &mainnet::FUNDING_STREAMS
1257        }
1258    }
1259
1260    /// Returns true if this network should allow transactions with transparent outputs
1261    /// that spend coinbase outputs.
1262    pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1263        if let Self::Testnet(params) = self {
1264            params.should_allow_unshielded_coinbase_spends()
1265        } else {
1266            false
1267        }
1268    }
1269
1270    /// Returns the list of founders' reward addresses for this network.
1271    pub fn founder_address_list(&self) -> &[&str] {
1272        match self {
1273            Network::Mainnet => &mainnet::FOUNDER_ADDRESS_LIST,
1274            Network::Testnet(_) => &testnet::FOUNDER_ADDRESS_LIST,
1275        }
1276    }
1277}