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