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