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_CHECKPOINTS},
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_CHECKPOINTS
516                .parse()
517                .map(Arc::new)
518                .expect("must be able to parse checkpoints"),
519        }
520    }
521}
522
523impl ParametersBuilder {
524    /// Sets the network name to be used in the [`Parameters`] being built.
525    pub fn with_network_name(
526        mut self,
527        network_name: impl fmt::Display,
528    ) -> Result<Self, ParametersBuilderError> {
529        let network_name = network_name.to_string();
530
531        if RESERVED_NETWORK_NAMES.contains(&network_name.as_str()) {
532            return Err(ParametersBuilderError::ReservedNetworkName {
533                network_name,
534                reserved_names: RESERVED_NETWORK_NAMES.to_vec(),
535            });
536        }
537
538        if network_name.len() > MAX_NETWORK_NAME_LENGTH {
539            return Err(ParametersBuilderError::NetworkNameTooLong {
540                network_name,
541                max_length: MAX_NETWORK_NAME_LENGTH,
542            });
543        }
544
545        if !network_name
546            .chars()
547            .all(|x| x.is_alphanumeric() || x == '_')
548        {
549            return Err(ParametersBuilderError::InvalidCharacter);
550        }
551
552        self.network_name = network_name;
553        Ok(self)
554    }
555
556    /// Sets the network name to be used in the [`Parameters`] being built.
557    pub fn with_network_magic(
558        mut self,
559        network_magic: Magic,
560    ) -> Result<Self, ParametersBuilderError> {
561        if [magics::MAINNET, magics::REGTEST]
562            .into_iter()
563            .any(|reserved_magic| network_magic == reserved_magic)
564        {
565            return Err(ParametersBuilderError::ReservedNetworkMagic);
566        }
567
568        self.network_magic = network_magic;
569
570        Ok(self)
571    }
572
573    /// Parses the hex-encoded block hash and sets it as the genesis hash in the [`Parameters`] being built.
574    pub fn with_genesis_hash(
575        mut self,
576        genesis_hash: impl fmt::Display,
577    ) -> Result<Self, ParametersBuilderError> {
578        self.genesis_hash = genesis_hash
579            .to_string()
580            .parse()
581            .map_err(|_| ParametersBuilderError::InvalidGenesisHash)?;
582        Ok(self)
583    }
584
585    /// Checks that the provided network upgrade activation heights are in the correct order, then
586    /// sets them as the new network upgrade activation heights.
587    pub fn with_activation_heights(
588        mut self,
589        ConfiguredActivationHeights {
590            before_overwinter,
591            overwinter,
592            sapling,
593            blossom,
594            heartwood,
595            canopy,
596            nu5,
597            nu6,
598            nu6_1,
599            nu7,
600            #[cfg(zcash_unstable = "zfuture")]
601            zfuture,
602        }: ConfiguredActivationHeights,
603    ) -> Result<Self, ParametersBuilderError> {
604        use NetworkUpgrade::*;
605
606        if self.should_lock_funding_stream_address_period {
607            return Err(ParametersBuilderError::LockFundingStreams);
608        }
609
610        // # Correctness
611        //
612        // These must be in order so that later network upgrades overwrite prior ones
613        // if multiple network upgrades are configured with the same activation height.
614        let activation_heights: BTreeMap<_, _> = {
615            let activation_heights = before_overwinter
616                .into_iter()
617                .map(|h| (h, BeforeOverwinter))
618                .chain(overwinter.into_iter().map(|h| (h, Overwinter)))
619                .chain(sapling.into_iter().map(|h| (h, Sapling)))
620                .chain(blossom.into_iter().map(|h| (h, Blossom)))
621                .chain(heartwood.into_iter().map(|h| (h, Heartwood)))
622                .chain(canopy.into_iter().map(|h| (h, Canopy)))
623                .chain(nu5.into_iter().map(|h| (h, Nu5)))
624                .chain(nu6.into_iter().map(|h| (h, Nu6)))
625                .chain(nu6_1.into_iter().map(|h| (h, Nu6_1)))
626                .chain(nu7.into_iter().map(|h| (h, Nu7)));
627
628            #[cfg(zcash_unstable = "zfuture")]
629            let activation_heights =
630                activation_heights.chain(zfuture.into_iter().map(|h| (h, ZFuture)));
631
632            activation_heights
633                .map(|(h, nu)| {
634                    let height = h
635                        .try_into()
636                        .map_err(|_| ParametersBuilderError::InvalidActivationHeight)?;
637                    Ok((height, nu))
638                })
639                .collect::<Result<BTreeMap<_, _>, _>>()?
640        };
641
642        let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect();
643
644        // Check that the provided network upgrade activation heights are in the same order by height as the default testnet activation heights
645        let mut activation_heights_iter = activation_heights.iter();
646        for expected_network_upgrade in NetworkUpgrade::iter() {
647            if !network_upgrades.contains(&expected_network_upgrade) {
648                continue;
649            } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
650                if height == Height(0) {
651                    return Err(ParametersBuilderError::InvalidHeightZero);
652                }
653
654                if network_upgrade != expected_network_upgrade {
655                    return Err(ParametersBuilderError::OutOfOrderUpgrades);
656                }
657            }
658        }
659
660        // # Correctness
661        //
662        // Height(0) must be reserved for the `NetworkUpgrade::Genesis`.
663        self.activation_heights.split_off(&Height(1));
664        self.activation_heights.extend(activation_heights);
665
666        Ok(self)
667    }
668
669    /// Sets the slow start interval to be used in the [`Parameters`] being built.
670    pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
671        self.slow_start_interval = slow_start_interval;
672        self
673    }
674
675    /// Sets funding streams to be used in the [`Parameters`] being built.
676    ///
677    /// # Panics
678    ///
679    /// If `funding_streams` is longer than `testnet::FUNDING_STREAMS`, and one
680    /// of the extra streams requires a default value.
681    pub fn with_funding_streams(mut self, funding_streams: Vec<ConfiguredFundingStreams>) -> Self {
682        self.funding_streams = funding_streams
683            .into_iter()
684            .enumerate()
685            .map(|(idx, streams)| {
686                let default_streams = testnet::FUNDING_STREAMS.get(idx).cloned();
687                streams.convert_with_default(default_streams)
688            })
689            .collect();
690        self.should_lock_funding_stream_address_period = true;
691        self
692    }
693
694    /// Clears funding streams from the [`Parameters`] being built.
695    pub fn clear_funding_streams(mut self) -> Self {
696        self.funding_streams = vec![];
697        self
698    }
699
700    /// Extends the configured funding streams to have as many recipients as are required for their
701    /// height ranges by repeating the recipients that have been configured.
702    ///
703    /// This should be called after configuring the desired network upgrade activation heights.
704    pub fn extend_funding_streams(mut self) -> Self {
705        let network = self.to_network_unchecked();
706
707        for funding_streams in &mut self.funding_streams {
708            funding_streams.extend_recipient_addresses(
709                num_funding_stream_addresses_required_for_height_range(
710                    funding_streams.height_range(),
711                    &network,
712                ),
713            );
714        }
715
716        self
717    }
718
719    /// Sets the target difficulty limit to be used in the [`Parameters`] being built.
720    // TODO: Accept a hex-encoded String instead?
721    pub fn with_target_difficulty_limit(
722        mut self,
723        target_difficulty_limit: impl Into<ExpandedDifficulty>,
724    ) -> Result<Self, ParametersBuilderError> {
725        self.target_difficulty_limit = target_difficulty_limit
726            .into()
727            .to_compact()
728            .to_expanded()
729            .ok_or(ParametersBuilderError::InvaildDifficultyLimits)?;
730        Ok(self)
731    }
732
733    /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
734    pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
735        self.disable_pow = disable_pow;
736        self
737    }
738
739    /// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
740    pub fn with_unshielded_coinbase_spends(
741        mut self,
742        should_allow_unshielded_coinbase_spends: bool,
743    ) -> Self {
744        self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends;
745        self
746    }
747
748    /// Sets the pre and post Blosssom halving intervals to be used in the [`Parameters`] being built.
749    pub fn with_halving_interval(
750        mut self,
751        pre_blossom_halving_interval: HeightDiff,
752    ) -> Result<Self, ParametersBuilderError> {
753        if self.should_lock_funding_stream_address_period {
754            return Err(ParametersBuilderError::HalvingIntervalAfterFundingStreams);
755        }
756
757        self.pre_blossom_halving_interval = pre_blossom_halving_interval;
758        self.post_blossom_halving_interval =
759            self.pre_blossom_halving_interval * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
760        Ok(self)
761    }
762
763    /// Sets the expected one-time lockbox disbursement outputs for this network
764    pub fn with_lockbox_disbursements(
765        mut self,
766        lockbox_disbursements: Vec<ConfiguredLockboxDisbursement>,
767    ) -> Self {
768        self.lockbox_disbursements = lockbox_disbursements
769            .into_iter()
770            .map(|ConfiguredLockboxDisbursement { address, amount }| (address, amount))
771            .collect();
772        self
773    }
774
775    /// Sets the checkpoints for the network as the provided [`ConfiguredCheckpoints`].
776    pub fn with_checkpoints(
777        mut self,
778        checkpoints: impl Into<ConfiguredCheckpoints>,
779    ) -> Result<Self, ParametersBuilderError> {
780        self.checkpoints = Arc::new(match checkpoints.into() {
781            ConfiguredCheckpoints::Default(true) => TESTNET_CHECKPOINTS
782                .parse()
783                .map_err(|_| ParametersBuilderError::InvalidCheckpointsFormat)?,
784            ConfiguredCheckpoints::Default(false) => {
785                CheckpointList::from_list([(block::Height(0), self.genesis_hash)])
786                    .map_err(|_| ParametersBuilderError::FailedToParseDefaultCheckpoint)?
787            }
788            ConfiguredCheckpoints::Path(path_buf) => {
789                let Ok(raw_checkpoints_str) = std::fs::read_to_string(&path_buf) else {
790                    return Err(ParametersBuilderError::FailedToReadCheckpointFile {
791                        path_buf: path_buf.clone(),
792                    });
793                };
794
795                raw_checkpoints_str
796                    .parse::<CheckpointList>()
797                    .map_err(|err| ParametersBuilderError::FailedToParseCheckpointFile {
798                        path_buf: path_buf.clone(),
799                        err: err.to_string(),
800                    })?
801            }
802            ConfiguredCheckpoints::HeightsAndHashes(items) => CheckpointList::from_list(items)
803                .map_err(|_| ParametersBuilderError::InvalidCustomCheckpoints)?,
804        });
805
806        Ok(self)
807    }
808
809    /// Clears checkpoints from the [`Parameters`] being built, keeping the genesis checkpoint.
810    pub fn clear_checkpoints(self) -> Result<Self, ParametersBuilderError> {
811        self.with_checkpoints(ConfiguredCheckpoints::Default(false))
812    }
813
814    /// Converts the builder to a [`Parameters`] struct
815    fn finish(self) -> Parameters {
816        let Self {
817            network_name,
818            network_magic,
819            genesis_hash,
820            activation_heights,
821            slow_start_interval,
822            funding_streams,
823            should_lock_funding_stream_address_period: _,
824            target_difficulty_limit,
825            disable_pow,
826            should_allow_unshielded_coinbase_spends,
827            pre_blossom_halving_interval,
828            post_blossom_halving_interval,
829            lockbox_disbursements,
830            checkpoints,
831        } = self;
832        Parameters {
833            network_name,
834            network_magic,
835            genesis_hash,
836            activation_heights,
837            slow_start_interval,
838            slow_start_shift: Height(slow_start_interval.0 / 2),
839            funding_streams,
840            target_difficulty_limit,
841            disable_pow,
842            should_allow_unshielded_coinbase_spends,
843            pre_blossom_halving_interval,
844            post_blossom_halving_interval,
845            lockbox_disbursements,
846            checkpoints,
847        }
848    }
849
850    /// Converts the builder to a configured [`Network::Testnet`]
851    fn to_network_unchecked(&self) -> Network {
852        Network::new_configured_testnet(self.clone().finish())
853    }
854
855    /// Checks funding streams and converts the builder to a configured [`Network::Testnet`]
856    pub fn to_network(self) -> Result<Network, ParametersBuilderError> {
857        let network = self.to_network_unchecked();
858
859        // Final check that the configured funding streams will be valid for these Testnet parameters.
860        for fs in &self.funding_streams {
861            // Check that the funding streams are valid for the configured Testnet parameters.
862            check_funding_stream_address_period(fs, &network);
863        }
864
865        // Final check that the configured checkpoints are valid for this network.
866        if network.checkpoint_list().hash(Height(0)) != Some(network.genesis_hash()) {
867            return Err(ParametersBuilderError::CheckpointGenesisMismatch);
868        }
869        if network.checkpoint_list().max_height() < network.mandatory_checkpoint_height() {
870            return Err(ParametersBuilderError::InsufficientCheckpointCoverage);
871        }
872
873        Ok(network)
874    }
875
876    /// Returns true if these [`Parameters`] should be compatible with the default Testnet parameters.
877    pub fn is_compatible_with_default_parameters(&self) -> bool {
878        let Self {
879            network_name: _,
880            network_magic,
881            genesis_hash,
882            activation_heights,
883            slow_start_interval,
884            funding_streams,
885            should_lock_funding_stream_address_period: _,
886            target_difficulty_limit,
887            disable_pow,
888            should_allow_unshielded_coinbase_spends,
889            pre_blossom_halving_interval,
890            post_blossom_halving_interval,
891            lockbox_disbursements,
892            checkpoints: _,
893        } = Self::default();
894
895        self.activation_heights == activation_heights
896            && self.network_magic == network_magic
897            && self.genesis_hash == genesis_hash
898            && self.slow_start_interval == slow_start_interval
899            && self.funding_streams == funding_streams
900            && self.target_difficulty_limit == target_difficulty_limit
901            && self.disable_pow == disable_pow
902            && self.should_allow_unshielded_coinbase_spends
903                == should_allow_unshielded_coinbase_spends
904            && self.pre_blossom_halving_interval == pre_blossom_halving_interval
905            && self.post_blossom_halving_interval == post_blossom_halving_interval
906            && self.lockbox_disbursements == lockbox_disbursements
907    }
908}
909
910/// A struct of parameters for configuring Regtest in Zebra.
911#[derive(Debug, Default, Clone)]
912pub struct RegtestParameters {
913    /// The configured network upgrade activation heights to use on Regtest
914    pub activation_heights: ConfiguredActivationHeights,
915    /// Configured funding streams
916    pub funding_streams: Option<Vec<ConfiguredFundingStreams>>,
917    /// Expected one-time lockbox disbursement outputs in NU6.1 activation block coinbase for Regtest
918    pub lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
919    /// Configured checkpointed block heights and hashes.
920    pub checkpoints: Option<ConfiguredCheckpoints>,
921    /// Whether funding stream addresses should be repeated to fill all required funding stream periods.
922    pub extend_funding_stream_addresses_as_required: Option<bool>,
923}
924
925impl From<ConfiguredActivationHeights> for RegtestParameters {
926    fn from(value: ConfiguredActivationHeights) -> Self {
927        Self {
928            activation_heights: value,
929            ..Default::default()
930        }
931    }
932}
933
934/// Network consensus parameters for test networks such as Regtest and the default Testnet.
935#[derive(Clone, Debug, Eq, PartialEq)]
936pub struct Parameters {
937    /// The name of this network to be used by the `Display` trait impl.
938    network_name: String,
939    /// The network magic, acts as an identifier for the network.
940    network_magic: Magic,
941    /// The genesis block hash
942    genesis_hash: block::Hash,
943    /// The network upgrade activation heights for this network.
944    activation_heights: BTreeMap<Height, NetworkUpgrade>,
945    /// Slow start interval for this network
946    slow_start_interval: Height,
947    /// Slow start shift for this network, always half the slow start interval
948    slow_start_shift: Height,
949    /// Funding streams for this network
950    funding_streams: Vec<FundingStreams>,
951    /// Target difficulty limit for this network
952    target_difficulty_limit: ExpandedDifficulty,
953    /// A flag for disabling proof-of-work checks when Zebra is validating blocks
954    disable_pow: bool,
955    /// Whether to allow transactions with transparent outputs to spend coinbase outputs,
956    /// similar to `fCoinbaseMustBeShielded` in zcashd.
957    should_allow_unshielded_coinbase_spends: bool,
958    /// Pre-Blossom halving interval for this network
959    pre_blossom_halving_interval: HeightDiff,
960    /// Post-Blossom halving interval for this network
961    post_blossom_halving_interval: HeightDiff,
962    /// Expected one-time lockbox disbursement outputs in NU6.1 activation block coinbase for this network
963    lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
964    /// List of checkpointed block heights and hashes
965    checkpoints: Arc<CheckpointList>,
966}
967
968impl Default for Parameters {
969    /// Returns an instance of the default public testnet [`Parameters`].
970    fn default() -> Self {
971        Self {
972            network_name: "Testnet".to_string(),
973            ..Self::build().finish()
974        }
975    }
976}
977
978impl Parameters {
979    /// Creates a new [`ParametersBuilder`].
980    pub fn build() -> ParametersBuilder {
981        ParametersBuilder::default()
982    }
983
984    /// Accepts a [`ConfiguredActivationHeights`].
985    ///
986    /// Creates an instance of [`Parameters`] with `Regtest` values.
987    pub fn new_regtest(
988        RegtestParameters {
989            activation_heights,
990            funding_streams,
991            lockbox_disbursements,
992            checkpoints,
993            extend_funding_stream_addresses_as_required,
994        }: RegtestParameters,
995    ) -> Result<Self, ParametersBuilderError> {
996        let mut parameters = Self::build()
997            .with_genesis_hash(REGTEST_GENESIS_HASH)?
998            // This value is chosen to match zcashd, see: <https://github.com/zcash/zcash/blob/master/src/chainparams.cpp#L654>
999            .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))?
1000            .with_disable_pow(true)
1001            .with_unshielded_coinbase_spends(true)
1002            .with_slow_start_interval(Height::MIN)
1003            // Removes default Testnet activation heights if not configured,
1004            // most network upgrades are disabled by default for Regtest in zcashd
1005            .with_activation_heights(activation_heights.for_regtest())?
1006            .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL)?
1007            .with_funding_streams(funding_streams.unwrap_or_default())
1008            .with_lockbox_disbursements(lockbox_disbursements.unwrap_or_default())
1009            .with_checkpoints(checkpoints.unwrap_or_default())?;
1010
1011        if Some(true) == extend_funding_stream_addresses_as_required {
1012            parameters = parameters.extend_funding_streams();
1013        }
1014
1015        Ok(Self {
1016            network_name: "Regtest".to_string(),
1017            network_magic: magics::REGTEST,
1018            ..parameters.finish()
1019        })
1020    }
1021
1022    /// Returns true if the instance of [`Parameters`] represents the default public Testnet.
1023    pub fn is_default_testnet(&self) -> bool {
1024        self == &Self::default()
1025    }
1026
1027    /// Returns true if the instance of [`Parameters`] represents Regtest.
1028    pub fn is_regtest(&self) -> bool {
1029        if self.network_magic != magics::REGTEST {
1030            return false;
1031        }
1032
1033        let Self {
1034            network_name,
1035            // Already checked network magic above
1036            network_magic: _,
1037            genesis_hash,
1038            // Activation heights are configurable on Regtest
1039            activation_heights: _,
1040            slow_start_interval,
1041            slow_start_shift,
1042            funding_streams: _,
1043            target_difficulty_limit,
1044            disable_pow,
1045            should_allow_unshielded_coinbase_spends,
1046            pre_blossom_halving_interval,
1047            post_blossom_halving_interval,
1048            lockbox_disbursements: _,
1049            checkpoints: _,
1050        } = Self::new_regtest(Default::default()).expect("default regtest parameters are valid");
1051
1052        self.network_name == network_name
1053            && self.genesis_hash == genesis_hash
1054            && self.slow_start_interval == slow_start_interval
1055            && self.slow_start_shift == slow_start_shift
1056            && self.target_difficulty_limit == target_difficulty_limit
1057            && self.disable_pow == disable_pow
1058            && self.should_allow_unshielded_coinbase_spends
1059                == should_allow_unshielded_coinbase_spends
1060            && self.pre_blossom_halving_interval == pre_blossom_halving_interval
1061            && self.post_blossom_halving_interval == post_blossom_halving_interval
1062    }
1063
1064    /// Returns the network name
1065    pub fn network_name(&self) -> &str {
1066        &self.network_name
1067    }
1068
1069    /// Returns the network magic
1070    pub fn network_magic(&self) -> Magic {
1071        self.network_magic
1072    }
1073
1074    /// Returns the genesis hash
1075    pub fn genesis_hash(&self) -> block::Hash {
1076        self.genesis_hash
1077    }
1078
1079    /// Returns the network upgrade activation heights
1080    pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
1081        &self.activation_heights
1082    }
1083
1084    /// Returns slow start interval for this network
1085    pub fn slow_start_interval(&self) -> Height {
1086        self.slow_start_interval
1087    }
1088
1089    /// Returns slow start shift for this network
1090    pub fn slow_start_shift(&self) -> Height {
1091        self.slow_start_shift
1092    }
1093
1094    /// Returns funding streams for this network.
1095    pub fn funding_streams(&self) -> &Vec<FundingStreams> {
1096        &self.funding_streams
1097    }
1098
1099    /// Returns the target difficulty limit for this network
1100    pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
1101        self.target_difficulty_limit
1102    }
1103
1104    /// Returns true if proof-of-work validation should be disabled for this network
1105    pub fn disable_pow(&self) -> bool {
1106        self.disable_pow
1107    }
1108
1109    /// Returns true if this network should allow transactions with transparent outputs
1110    /// that spend coinbase outputs.
1111    pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1112        self.should_allow_unshielded_coinbase_spends
1113    }
1114
1115    /// Returns the pre-Blossom halving interval for this network
1116    pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
1117        self.pre_blossom_halving_interval
1118    }
1119
1120    /// Returns the post-Blossom halving interval for this network
1121    pub fn post_blossom_halving_interval(&self) -> HeightDiff {
1122        self.post_blossom_halving_interval
1123    }
1124
1125    /// Returns the expected total value of the sum of all NU6.1 one-time lockbox disbursement output values for this network.
1126    pub fn lockbox_disbursement_total_amount(&self) -> Amount<NonNegative> {
1127        self.lockbox_disbursements()
1128            .into_iter()
1129            .map(|(_addr, amount)| amount)
1130            .reduce(|a, b| (a + b).expect("sum of configured amounts should be valid"))
1131            .unwrap_or_default()
1132    }
1133
1134    /// Returns the expected NU6.1 lockbox disbursement outputs for this network.
1135    pub fn lockbox_disbursements(&self) -> Vec<(transparent::Address, Amount<NonNegative>)> {
1136        self.lockbox_disbursements
1137            .iter()
1138            .map(|(addr, amount)| {
1139                (
1140                    addr.parse().expect("hard-coded address must deserialize"),
1141                    *amount,
1142                )
1143            })
1144            .collect()
1145    }
1146
1147    /// Returns the checkpoints for this network.
1148    pub fn checkpoints(&self) -> Arc<CheckpointList> {
1149        self.checkpoints.clone()
1150    }
1151}
1152
1153impl Network {
1154    /// Returns the parameters of this network if it is a Testnet.
1155    pub fn parameters(&self) -> Option<Arc<Parameters>> {
1156        if let Self::Testnet(parameters) = self {
1157            Some(parameters.clone())
1158        } else {
1159            None
1160        }
1161    }
1162
1163    /// Returns true if proof-of-work validation should be disabled for this network
1164    pub fn disable_pow(&self) -> bool {
1165        if let Self::Testnet(params) = self {
1166            params.disable_pow()
1167        } else {
1168            false
1169        }
1170    }
1171
1172    /// Returns slow start interval for this network
1173    pub fn slow_start_interval(&self) -> Height {
1174        if let Self::Testnet(params) = self {
1175            params.slow_start_interval()
1176        } else {
1177            SLOW_START_INTERVAL
1178        }
1179    }
1180
1181    /// Returns slow start shift for this network
1182    pub fn slow_start_shift(&self) -> Height {
1183        if let Self::Testnet(params) = self {
1184            params.slow_start_shift()
1185        } else {
1186            SLOW_START_SHIFT
1187        }
1188    }
1189
1190    /// Returns post-Canopy funding streams for this network at the provided height
1191    pub fn funding_streams(&self, height: Height) -> Option<&FundingStreams> {
1192        self.all_funding_streams()
1193            .iter()
1194            .find(|&streams| streams.height_range().contains(&height))
1195    }
1196
1197    /// Returns post-Canopy funding streams for this network at the provided height
1198    pub fn all_funding_streams(&self) -> &Vec<FundingStreams> {
1199        if let Self::Testnet(params) = self {
1200            params.funding_streams()
1201        } else {
1202            &mainnet::FUNDING_STREAMS
1203        }
1204    }
1205
1206    /// Returns true if this network should allow transactions with transparent outputs
1207    /// that spend coinbase outputs.
1208    pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1209        if let Self::Testnet(params) = self {
1210            params.should_allow_unshielded_coinbase_spends()
1211        } else {
1212            false
1213        }
1214    }
1215}