zebra_chain/parameters/network/
testnet.rs

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