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