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