1use std::{collections::BTreeMap, fmt, sync::Arc};
4
5use crate::{
6 amount::{Amount, NonNegative},
7 block::{self, Height, HeightDiff},
8 parameters::{
9 checkpoint::list::{CheckpointList, TESTNET_CHECKPOINTS},
10 constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT},
11 network::error::ParametersBuilderError,
12 network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
13 subsidy::{
14 constants::mainnet,
15 constants::testnet,
16 constants::{
17 BLOSSOM_POW_TARGET_SPACING_RATIO, FUNDING_STREAM_RECEIVER_DENOMINATOR,
18 POST_BLOSSOM_HALVING_INTERVAL, PRE_BLOSSOM_HALVING_INTERVAL,
19 },
20 funding_stream_address_period, FundingStreamReceiver, FundingStreamRecipient,
21 FundingStreams,
22 },
23 Network, NetworkKind, NetworkUpgrade,
24 },
25 transparent,
26 work::difficulty::{ExpandedDifficulty, U256},
27};
28
29use super::magic::Magic;
30
31pub const RESERVED_NETWORK_NAMES: [&str; 6] = [
33 "Mainnet",
34 "Testnet",
35 "Regtest",
36 "MainnetKind",
37 "TestnetKind",
38 "RegtestKind",
39];
40
41pub const MAX_NETWORK_NAME_LENGTH: usize = 30;
43
44pub const MAX_HRP_LENGTH: usize = 30;
46
47const REGTEST_GENESIS_HASH: &str =
49 "029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327";
50
51const TESTNET_GENESIS_HASH: &str =
53 "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38";
54
55const PRE_BLOSSOM_REGTEST_HALVING_INTERVAL: HeightDiff = 144;
58
59#[derive(Serialize, Deserialize, Clone, Debug)]
61#[serde(deny_unknown_fields)]
62pub struct ConfiguredFundingStreamRecipient {
63 pub receiver: FundingStreamReceiver,
65 pub numerator: u64,
67 pub addresses: Option<Vec<String>>,
69}
70
71impl ConfiguredFundingStreamRecipient {
72 pub fn new_for(receiver: FundingStreamReceiver) -> Self {
75 use FundingStreamReceiver::*;
76 match receiver {
77 Ecc => Self {
78 receiver: Ecc,
79 numerator: 7,
80 addresses: Some(
81 testnet::FUNDING_STREAM_ECC_ADDRESSES
82 .map(ToString::to_string)
83 .to_vec(),
84 ),
85 },
86 ZcashFoundation => Self {
87 receiver: ZcashFoundation,
88 numerator: 5,
89 addresses: Some(
90 testnet::FUNDING_STREAM_ZF_ADDRESSES
91 .map(ToString::to_string)
92 .to_vec(),
93 ),
94 },
95 MajorGrants => Self {
96 receiver: MajorGrants,
97 numerator: 8,
98 addresses: Some(
99 testnet::FUNDING_STREAM_MG_ADDRESSES
100 .map(ToString::to_string)
101 .to_vec(),
102 ),
103 },
104 Deferred => Self {
105 receiver,
106 numerator: 0,
107 addresses: None,
108 },
109 }
110 }
111
112 pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) {
114 (
115 self.receiver,
116 FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()),
117 )
118 }
119}
120
121#[derive(Serialize, Deserialize, Clone, Debug)]
123#[serde(deny_unknown_fields)]
124pub struct ConfiguredLockboxDisbursement {
125 pub address: String,
127 pub amount: Amount<NonNegative>,
129}
130
131#[derive(Serialize, Deserialize, Clone, Default, Debug)]
133#[serde(deny_unknown_fields)]
134pub struct ConfiguredFundingStreams {
135 pub height_range: Option<std::ops::Range<Height>>,
137 pub recipients: Option<Vec<ConfiguredFundingStreamRecipient>>,
139}
140
141impl From<&FundingStreams> for ConfiguredFundingStreams {
142 fn from(value: &FundingStreams) -> Self {
143 Self {
144 height_range: Some(value.height_range().clone()),
145 recipients: Some(
146 value
147 .recipients()
148 .iter()
149 .map(|(receiver, recipient)| ConfiguredFundingStreamRecipient {
150 receiver: *receiver,
151 numerator: recipient.numerator(),
152 addresses: Some(
153 recipient
154 .addresses()
155 .iter()
156 .map(ToString::to_string)
157 .collect(),
158 ),
159 })
160 .collect(),
161 ),
162 }
163 }
164}
165
166impl From<(transparent::Address, Amount<NonNegative>)> for ConfiguredLockboxDisbursement {
167 fn from((address, amount): (transparent::Address, Amount<NonNegative>)) -> Self {
168 Self {
169 address: address.to_string(),
170 amount,
171 }
172 }
173}
174
175impl From<&BTreeMap<Height, NetworkUpgrade>> for ConfiguredActivationHeights {
176 fn from(activation_heights: &BTreeMap<Height, NetworkUpgrade>) -> Self {
177 let mut configured_activation_heights = ConfiguredActivationHeights::default();
178
179 for (height, network_upgrade) in activation_heights {
180 let field = match network_upgrade {
181 NetworkUpgrade::BeforeOverwinter => {
182 &mut configured_activation_heights.before_overwinter
183 }
184 NetworkUpgrade::Overwinter => &mut configured_activation_heights.overwinter,
185 NetworkUpgrade::Sapling => &mut configured_activation_heights.sapling,
186 NetworkUpgrade::Blossom => &mut configured_activation_heights.blossom,
187 NetworkUpgrade::Heartwood => &mut configured_activation_heights.heartwood,
188 NetworkUpgrade::Canopy => &mut configured_activation_heights.canopy,
189 NetworkUpgrade::Nu5 => &mut configured_activation_heights.nu5,
190 NetworkUpgrade::Nu6 => &mut configured_activation_heights.nu6,
191 NetworkUpgrade::Nu6_1 => &mut configured_activation_heights.nu6_1,
192 NetworkUpgrade::Nu7 => &mut configured_activation_heights.nu7,
193 #[cfg(zcash_unstable = "zfuture")]
194 NetworkUpgrade::ZFuture => &mut configured_activation_heights.zfuture,
195 NetworkUpgrade::Genesis => continue,
196 };
197
198 *field = Some(height.0)
199 }
200
201 configured_activation_heights
202 }
203}
204
205impl From<BTreeMap<Height, NetworkUpgrade>> for ConfiguredActivationHeights {
206 fn from(value: BTreeMap<Height, NetworkUpgrade>) -> Self {
207 Self::from(&value)
208 }
209}
210
211impl ConfiguredFundingStreams {
212 fn convert_with_default(
219 self,
220 default_funding_streams: Option<FundingStreams>,
221 ) -> FundingStreams {
222 let height_range = self.height_range.unwrap_or_else(|| {
223 default_funding_streams
224 .as_ref()
225 .expect("default required")
226 .height_range()
227 .clone()
228 });
229
230 let recipients = self
231 .recipients
232 .map(|recipients| {
233 recipients
234 .into_iter()
235 .map(ConfiguredFundingStreamRecipient::into_recipient)
236 .collect()
237 })
238 .unwrap_or_else(|| {
239 default_funding_streams
240 .as_ref()
241 .expect("default required")
242 .recipients()
243 .clone()
244 });
245
246 assert!(
247 height_range.start <= height_range.end,
248 "funding stream end height must be above start height"
249 );
250
251 let funding_streams = FundingStreams::new(height_range.clone(), recipients);
252
253 let sum_numerators: u64 = funding_streams
256 .recipients()
257 .values()
258 .map(|r| r.numerator())
259 .sum();
260
261 assert!(
262 sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR,
263 "sum of funding stream numerators must not be \
264 greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}"
265 );
266
267 funding_streams
268 }
269
270 pub fn into_funding_streams_unchecked(self) -> FundingStreams {
276 let height_range = self.height_range.expect("must have height range");
277 let recipients = self
278 .recipients
279 .into_iter()
280 .flat_map(|recipients| {
281 recipients
282 .into_iter()
283 .map(ConfiguredFundingStreamRecipient::into_recipient)
284 })
285 .collect();
286
287 FundingStreams::new(height_range, recipients)
288 }
289}
290
291fn num_funding_stream_addresses_required_for_height_range(
293 height_range: &std::ops::Range<Height>,
294 network: &Network,
295) -> usize {
296 1u32.checked_add(funding_stream_address_period(
297 height_range
298 .end
299 .previous()
300 .expect("end height must be above start height and genesis height"),
301 network,
302 ))
303 .expect("no overflow should happen in this sum")
304 .checked_sub(funding_stream_address_period(height_range.start, network))
305 .expect("no overflow should happen in this sub") as usize
306}
307
308fn check_funding_stream_address_period(funding_streams: &FundingStreams, network: &Network) {
311 let expected_min_num_addresses = num_funding_stream_addresses_required_for_height_range(
312 funding_streams.height_range(),
313 network,
314 );
315
316 for (&receiver, recipient) in funding_streams.recipients() {
317 if receiver == FundingStreamReceiver::Deferred {
318 continue;
320 }
321
322 let num_addresses = recipient.addresses().len();
323 assert!(
324 num_addresses >= expected_min_num_addresses,
325 "recipients must have a sufficient number of addresses for height range, \
326 minimum num addresses required: {expected_min_num_addresses}, only {num_addresses} were provided.\
327 receiver: {receiver:?}, recipient: {recipient:?}"
328 );
329
330 for address in recipient.addresses() {
331 assert_eq!(
332 address.network_kind(),
333 NetworkKind::Testnet,
334 "configured funding stream addresses must be for Testnet"
335 );
336 }
337 }
338}
339
340#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq)]
342#[serde(rename_all = "PascalCase", deny_unknown_fields)]
343pub struct ConfiguredActivationHeights {
344 pub before_overwinter: Option<u32>,
346 pub overwinter: Option<u32>,
348 pub sapling: Option<u32>,
350 pub blossom: Option<u32>,
352 pub heartwood: Option<u32>,
354 pub canopy: Option<u32>,
356 #[serde(rename = "NU5")]
358 pub nu5: Option<u32>,
359 #[serde(rename = "NU6")]
361 pub nu6: Option<u32>,
362 #[serde(rename = "NU6.1")]
364 pub nu6_1: Option<u32>,
365 #[serde(rename = "NU7")]
367 pub nu7: Option<u32>,
368 #[serde(rename = "ZFuture")]
370 #[cfg(zcash_unstable = "zfuture")]
371 pub zfuture: Option<u32>,
372}
373
374impl ConfiguredActivationHeights {
375 fn for_regtest(self) -> Self {
378 let Self {
379 before_overwinter,
380 overwinter,
381 sapling,
382 blossom,
383 heartwood,
384 canopy,
385 nu5,
386 nu6,
387 nu6_1,
388 nu7,
389 #[cfg(zcash_unstable = "zfuture")]
390 zfuture,
391 } = self;
392
393 let overwinter = overwinter.or(before_overwinter).or(Some(1));
394 let sapling = sapling.or(overwinter);
395 let blossom = blossom.or(sapling);
396 let heartwood = heartwood.or(blossom);
397 let canopy = canopy.or(heartwood);
398
399 Self {
400 before_overwinter,
401 overwinter,
402 sapling,
403 blossom,
404 heartwood,
405 canopy,
406 nu5,
407 nu6,
408 nu6_1,
409 nu7,
410 #[cfg(zcash_unstable = "zfuture")]
411 zfuture,
412 }
413 }
414}
415
416#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
419#[serde(untagged)]
420pub enum ConfiguredCheckpoints {
421 Default(bool),
423 Path(std::path::PathBuf),
425 HeightsAndHashes(Vec<(block::Height, block::Hash)>),
427}
428
429impl Default for ConfiguredCheckpoints {
430 fn default() -> Self {
431 Self::Default(false)
432 }
433}
434
435impl From<Arc<CheckpointList>> for ConfiguredCheckpoints {
436 fn from(value: Arc<CheckpointList>) -> Self {
437 Self::HeightsAndHashes(value.iter_cloned().collect())
438 }
439}
440
441impl From<bool> for ConfiguredCheckpoints {
442 fn from(value: bool) -> Self {
443 Self::Default(value)
444 }
445}
446
447#[derive(Clone, Debug, Eq, PartialEq)]
449pub struct ParametersBuilder {
450 network_name: String,
452 network_magic: Magic,
454 genesis_hash: block::Hash,
456 activation_heights: BTreeMap<Height, NetworkUpgrade>,
458 slow_start_interval: Height,
460 funding_streams: Vec<FundingStreams>,
462 should_lock_funding_stream_address_period: bool,
465 target_difficulty_limit: ExpandedDifficulty,
467 disable_pow: bool,
469 should_allow_unshielded_coinbase_spends: bool,
472 pre_blossom_halving_interval: HeightDiff,
474 post_blossom_halving_interval: HeightDiff,
476 lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
478 checkpoints: Arc<CheckpointList>,
480}
481
482impl Default for ParametersBuilder {
483 fn default() -> Self {
485 Self {
486 network_name: "UnknownTestnet".to_string(),
487 network_magic: magics::TESTNET,
488 activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
492 genesis_hash: TESTNET_GENESIS_HASH
493 .parse()
494 .expect("hard-coded hash parses"),
495 slow_start_interval: SLOW_START_INTERVAL,
496 target_difficulty_limit: ExpandedDifficulty::from((U256::one() << 251) - 1)
502 .to_compact()
503 .to_expanded()
504 .expect("difficulty limits are valid expanded values"),
505 disable_pow: false,
506 funding_streams: testnet::FUNDING_STREAMS.clone(),
507 should_lock_funding_stream_address_period: false,
508 pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL,
509 post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL,
510 should_allow_unshielded_coinbase_spends: false,
511 lockbox_disbursements: testnet::NU6_1_LOCKBOX_DISBURSEMENTS
512 .iter()
513 .map(|(addr, amount)| (addr.to_string(), *amount))
514 .collect(),
515 checkpoints: TESTNET_CHECKPOINTS
516 .parse()
517 .map(Arc::new)
518 .expect("must be able to parse checkpoints"),
519 }
520 }
521}
522
523impl ParametersBuilder {
524 pub fn with_network_name(
526 mut self,
527 network_name: impl fmt::Display,
528 ) -> Result<Self, ParametersBuilderError> {
529 let network_name = network_name.to_string();
530
531 if RESERVED_NETWORK_NAMES.contains(&network_name.as_str()) {
532 return Err(ParametersBuilderError::ReservedNetworkName {
533 network_name,
534 reserved_names: RESERVED_NETWORK_NAMES.to_vec(),
535 });
536 }
537
538 if network_name.len() > MAX_NETWORK_NAME_LENGTH {
539 return Err(ParametersBuilderError::NetworkNameTooLong {
540 network_name,
541 max_length: MAX_NETWORK_NAME_LENGTH,
542 });
543 }
544
545 if !network_name
546 .chars()
547 .all(|x| x.is_alphanumeric() || x == '_')
548 {
549 return Err(ParametersBuilderError::InvalidCharacter);
550 }
551
552 self.network_name = network_name;
553 Ok(self)
554 }
555
556 pub fn with_network_magic(
558 mut self,
559 network_magic: Magic,
560 ) -> Result<Self, ParametersBuilderError> {
561 if [magics::MAINNET, magics::REGTEST]
562 .into_iter()
563 .any(|reserved_magic| network_magic == reserved_magic)
564 {
565 return Err(ParametersBuilderError::ReservedNetworkMagic);
566 }
567
568 self.network_magic = network_magic;
569
570 Ok(self)
571 }
572
573 pub fn with_genesis_hash(
575 mut self,
576 genesis_hash: impl fmt::Display,
577 ) -> Result<Self, ParametersBuilderError> {
578 self.genesis_hash = genesis_hash
579 .to_string()
580 .parse()
581 .map_err(|_| ParametersBuilderError::InvalidGenesisHash)?;
582 Ok(self)
583 }
584
585 pub fn with_activation_heights(
588 mut self,
589 ConfiguredActivationHeights {
590 before_overwinter,
591 overwinter,
592 sapling,
593 blossom,
594 heartwood,
595 canopy,
596 nu5,
597 nu6,
598 nu6_1,
599 nu7,
600 #[cfg(zcash_unstable = "zfuture")]
601 zfuture,
602 }: ConfiguredActivationHeights,
603 ) -> Result<Self, ParametersBuilderError> {
604 use NetworkUpgrade::*;
605
606 if self.should_lock_funding_stream_address_period {
607 return Err(ParametersBuilderError::LockFundingStreams);
608 }
609
610 let activation_heights: BTreeMap<_, _> = {
615 let activation_heights = before_overwinter
616 .into_iter()
617 .map(|h| (h, BeforeOverwinter))
618 .chain(overwinter.into_iter().map(|h| (h, Overwinter)))
619 .chain(sapling.into_iter().map(|h| (h, Sapling)))
620 .chain(blossom.into_iter().map(|h| (h, Blossom)))
621 .chain(heartwood.into_iter().map(|h| (h, Heartwood)))
622 .chain(canopy.into_iter().map(|h| (h, Canopy)))
623 .chain(nu5.into_iter().map(|h| (h, Nu5)))
624 .chain(nu6.into_iter().map(|h| (h, Nu6)))
625 .chain(nu6_1.into_iter().map(|h| (h, Nu6_1)))
626 .chain(nu7.into_iter().map(|h| (h, Nu7)));
627
628 #[cfg(zcash_unstable = "zfuture")]
629 let activation_heights =
630 activation_heights.chain(zfuture.into_iter().map(|h| (h, ZFuture)));
631
632 activation_heights
633 .map(|(h, nu)| {
634 let height = h
635 .try_into()
636 .map_err(|_| ParametersBuilderError::InvalidActivationHeight)?;
637 Ok((height, nu))
638 })
639 .collect::<Result<BTreeMap<_, _>, _>>()?
640 };
641
642 let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect();
643
644 let mut activation_heights_iter = activation_heights.iter();
646 for expected_network_upgrade in NetworkUpgrade::iter() {
647 if !network_upgrades.contains(&expected_network_upgrade) {
648 continue;
649 } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
650 if height == Height(0) {
651 return Err(ParametersBuilderError::InvalidHeightZero);
652 }
653
654 if network_upgrade != expected_network_upgrade {
655 return Err(ParametersBuilderError::OutOfOrderUpgrades);
656 }
657 }
658 }
659
660 self.activation_heights.split_off(&Height(1));
664 self.activation_heights.extend(activation_heights);
665
666 Ok(self)
667 }
668
669 pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
671 self.slow_start_interval = slow_start_interval;
672 self
673 }
674
675 pub fn with_funding_streams(mut self, funding_streams: Vec<ConfiguredFundingStreams>) -> Self {
682 self.funding_streams = funding_streams
683 .into_iter()
684 .enumerate()
685 .map(|(idx, streams)| {
686 let default_streams = testnet::FUNDING_STREAMS.get(idx).cloned();
687 streams.convert_with_default(default_streams)
688 })
689 .collect();
690 self.should_lock_funding_stream_address_period = true;
691 self
692 }
693
694 pub fn clear_funding_streams(mut self) -> Self {
696 self.funding_streams = vec![];
697 self
698 }
699
700 pub fn extend_funding_streams(mut self) -> Self {
705 let network = self.to_network_unchecked();
706
707 for funding_streams in &mut self.funding_streams {
708 funding_streams.extend_recipient_addresses(
709 num_funding_stream_addresses_required_for_height_range(
710 funding_streams.height_range(),
711 &network,
712 ),
713 );
714 }
715
716 self
717 }
718
719 pub fn with_target_difficulty_limit(
722 mut self,
723 target_difficulty_limit: impl Into<ExpandedDifficulty>,
724 ) -> Result<Self, ParametersBuilderError> {
725 self.target_difficulty_limit = target_difficulty_limit
726 .into()
727 .to_compact()
728 .to_expanded()
729 .ok_or(ParametersBuilderError::InvaildDifficultyLimits)?;
730 Ok(self)
731 }
732
733 pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
735 self.disable_pow = disable_pow;
736 self
737 }
738
739 pub fn with_unshielded_coinbase_spends(
741 mut self,
742 should_allow_unshielded_coinbase_spends: bool,
743 ) -> Self {
744 self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends;
745 self
746 }
747
748 pub fn with_halving_interval(
750 mut self,
751 pre_blossom_halving_interval: HeightDiff,
752 ) -> Result<Self, ParametersBuilderError> {
753 if self.should_lock_funding_stream_address_period {
754 return Err(ParametersBuilderError::HalvingIntervalAfterFundingStreams);
755 }
756
757 self.pre_blossom_halving_interval = pre_blossom_halving_interval;
758 self.post_blossom_halving_interval =
759 self.pre_blossom_halving_interval * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
760 Ok(self)
761 }
762
763 pub fn with_lockbox_disbursements(
765 mut self,
766 lockbox_disbursements: Vec<ConfiguredLockboxDisbursement>,
767 ) -> Self {
768 self.lockbox_disbursements = lockbox_disbursements
769 .into_iter()
770 .map(|ConfiguredLockboxDisbursement { address, amount }| (address, amount))
771 .collect();
772 self
773 }
774
775 pub fn with_checkpoints(
777 mut self,
778 checkpoints: impl Into<ConfiguredCheckpoints>,
779 ) -> Result<Self, ParametersBuilderError> {
780 self.checkpoints = Arc::new(match checkpoints.into() {
781 ConfiguredCheckpoints::Default(true) => TESTNET_CHECKPOINTS
782 .parse()
783 .map_err(|_| ParametersBuilderError::InvalidCheckpointsFormat)?,
784 ConfiguredCheckpoints::Default(false) => {
785 CheckpointList::from_list([(block::Height(0), self.genesis_hash)])
786 .map_err(|_| ParametersBuilderError::FailedToParseDefaultCheckpoint)?
787 }
788 ConfiguredCheckpoints::Path(path_buf) => {
789 let Ok(raw_checkpoints_str) = std::fs::read_to_string(&path_buf) else {
790 return Err(ParametersBuilderError::FailedToReadCheckpointFile {
791 path_buf: path_buf.clone(),
792 });
793 };
794
795 raw_checkpoints_str
796 .parse::<CheckpointList>()
797 .map_err(|err| ParametersBuilderError::FailedToParseCheckpointFile {
798 path_buf: path_buf.clone(),
799 err: err.to_string(),
800 })?
801 }
802 ConfiguredCheckpoints::HeightsAndHashes(items) => CheckpointList::from_list(items)
803 .map_err(|_| ParametersBuilderError::InvalidCustomCheckpoints)?,
804 });
805
806 Ok(self)
807 }
808
809 pub fn clear_checkpoints(self) -> Result<Self, ParametersBuilderError> {
811 self.with_checkpoints(ConfiguredCheckpoints::Default(false))
812 }
813
814 fn finish(self) -> Parameters {
816 let Self {
817 network_name,
818 network_magic,
819 genesis_hash,
820 activation_heights,
821 slow_start_interval,
822 funding_streams,
823 should_lock_funding_stream_address_period: _,
824 target_difficulty_limit,
825 disable_pow,
826 should_allow_unshielded_coinbase_spends,
827 pre_blossom_halving_interval,
828 post_blossom_halving_interval,
829 lockbox_disbursements,
830 checkpoints,
831 } = self;
832 Parameters {
833 network_name,
834 network_magic,
835 genesis_hash,
836 activation_heights,
837 slow_start_interval,
838 slow_start_shift: Height(slow_start_interval.0 / 2),
839 funding_streams,
840 target_difficulty_limit,
841 disable_pow,
842 should_allow_unshielded_coinbase_spends,
843 pre_blossom_halving_interval,
844 post_blossom_halving_interval,
845 lockbox_disbursements,
846 checkpoints,
847 }
848 }
849
850 fn to_network_unchecked(&self) -> Network {
852 Network::new_configured_testnet(self.clone().finish())
853 }
854
855 pub fn to_network(self) -> Result<Network, ParametersBuilderError> {
857 let network = self.to_network_unchecked();
858
859 for fs in &self.funding_streams {
861 check_funding_stream_address_period(fs, &network);
863 }
864
865 if network.checkpoint_list().hash(Height(0)) != Some(network.genesis_hash()) {
867 return Err(ParametersBuilderError::CheckpointGenesisMismatch);
868 }
869 if network.checkpoint_list().max_height() < network.mandatory_checkpoint_height() {
870 return Err(ParametersBuilderError::InsufficientCheckpointCoverage);
871 }
872
873 Ok(network)
874 }
875
876 pub fn is_compatible_with_default_parameters(&self) -> bool {
878 let Self {
879 network_name: _,
880 network_magic,
881 genesis_hash,
882 activation_heights,
883 slow_start_interval,
884 funding_streams,
885 should_lock_funding_stream_address_period: _,
886 target_difficulty_limit,
887 disable_pow,
888 should_allow_unshielded_coinbase_spends,
889 pre_blossom_halving_interval,
890 post_blossom_halving_interval,
891 lockbox_disbursements,
892 checkpoints: _,
893 } = Self::default();
894
895 self.activation_heights == activation_heights
896 && self.network_magic == network_magic
897 && self.genesis_hash == genesis_hash
898 && self.slow_start_interval == slow_start_interval
899 && self.funding_streams == funding_streams
900 && self.target_difficulty_limit == target_difficulty_limit
901 && self.disable_pow == disable_pow
902 && self.should_allow_unshielded_coinbase_spends
903 == should_allow_unshielded_coinbase_spends
904 && self.pre_blossom_halving_interval == pre_blossom_halving_interval
905 && self.post_blossom_halving_interval == post_blossom_halving_interval
906 && self.lockbox_disbursements == lockbox_disbursements
907 }
908}
909
910#[derive(Debug, Default, Clone)]
912pub struct RegtestParameters {
913 pub activation_heights: ConfiguredActivationHeights,
915 pub funding_streams: Option<Vec<ConfiguredFundingStreams>>,
917 pub lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
919 pub checkpoints: Option<ConfiguredCheckpoints>,
921 pub extend_funding_stream_addresses_as_required: Option<bool>,
923}
924
925impl From<ConfiguredActivationHeights> for RegtestParameters {
926 fn from(value: ConfiguredActivationHeights) -> Self {
927 Self {
928 activation_heights: value,
929 ..Default::default()
930 }
931 }
932}
933
934#[derive(Clone, Debug, Eq, PartialEq)]
936pub struct Parameters {
937 network_name: String,
939 network_magic: Magic,
941 genesis_hash: block::Hash,
943 activation_heights: BTreeMap<Height, NetworkUpgrade>,
945 slow_start_interval: Height,
947 slow_start_shift: Height,
949 funding_streams: Vec<FundingStreams>,
951 target_difficulty_limit: ExpandedDifficulty,
953 disable_pow: bool,
955 should_allow_unshielded_coinbase_spends: bool,
958 pre_blossom_halving_interval: HeightDiff,
960 post_blossom_halving_interval: HeightDiff,
962 lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
964 checkpoints: Arc<CheckpointList>,
966}
967
968impl Default for Parameters {
969 fn default() -> Self {
971 Self {
972 network_name: "Testnet".to_string(),
973 ..Self::build().finish()
974 }
975 }
976}
977
978impl Parameters {
979 pub fn build() -> ParametersBuilder {
981 ParametersBuilder::default()
982 }
983
984 pub fn new_regtest(
988 RegtestParameters {
989 activation_heights,
990 funding_streams,
991 lockbox_disbursements,
992 checkpoints,
993 extend_funding_stream_addresses_as_required,
994 }: RegtestParameters,
995 ) -> Result<Self, ParametersBuilderError> {
996 let mut parameters = Self::build()
997 .with_genesis_hash(REGTEST_GENESIS_HASH)?
998 .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))?
1000 .with_disable_pow(true)
1001 .with_unshielded_coinbase_spends(true)
1002 .with_slow_start_interval(Height::MIN)
1003 .with_activation_heights(activation_heights.for_regtest())?
1006 .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL)?
1007 .with_funding_streams(funding_streams.unwrap_or_default())
1008 .with_lockbox_disbursements(lockbox_disbursements.unwrap_or_default())
1009 .with_checkpoints(checkpoints.unwrap_or_default())?;
1010
1011 if Some(true) == extend_funding_stream_addresses_as_required {
1012 parameters = parameters.extend_funding_streams();
1013 }
1014
1015 Ok(Self {
1016 network_name: "Regtest".to_string(),
1017 network_magic: magics::REGTEST,
1018 ..parameters.finish()
1019 })
1020 }
1021
1022 pub fn is_default_testnet(&self) -> bool {
1024 self == &Self::default()
1025 }
1026
1027 pub fn is_regtest(&self) -> bool {
1029 if self.network_magic != magics::REGTEST {
1030 return false;
1031 }
1032
1033 let Self {
1034 network_name,
1035 network_magic: _,
1037 genesis_hash,
1038 activation_heights: _,
1040 slow_start_interval,
1041 slow_start_shift,
1042 funding_streams: _,
1043 target_difficulty_limit,
1044 disable_pow,
1045 should_allow_unshielded_coinbase_spends,
1046 pre_blossom_halving_interval,
1047 post_blossom_halving_interval,
1048 lockbox_disbursements: _,
1049 checkpoints: _,
1050 } = Self::new_regtest(Default::default()).expect("default regtest parameters are valid");
1051
1052 self.network_name == network_name
1053 && self.genesis_hash == genesis_hash
1054 && self.slow_start_interval == slow_start_interval
1055 && self.slow_start_shift == slow_start_shift
1056 && self.target_difficulty_limit == target_difficulty_limit
1057 && self.disable_pow == disable_pow
1058 && self.should_allow_unshielded_coinbase_spends
1059 == should_allow_unshielded_coinbase_spends
1060 && self.pre_blossom_halving_interval == pre_blossom_halving_interval
1061 && self.post_blossom_halving_interval == post_blossom_halving_interval
1062 }
1063
1064 pub fn network_name(&self) -> &str {
1066 &self.network_name
1067 }
1068
1069 pub fn network_magic(&self) -> Magic {
1071 self.network_magic
1072 }
1073
1074 pub fn genesis_hash(&self) -> block::Hash {
1076 self.genesis_hash
1077 }
1078
1079 pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
1081 &self.activation_heights
1082 }
1083
1084 pub fn slow_start_interval(&self) -> Height {
1086 self.slow_start_interval
1087 }
1088
1089 pub fn slow_start_shift(&self) -> Height {
1091 self.slow_start_shift
1092 }
1093
1094 pub fn funding_streams(&self) -> &Vec<FundingStreams> {
1096 &self.funding_streams
1097 }
1098
1099 pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
1101 self.target_difficulty_limit
1102 }
1103
1104 pub fn disable_pow(&self) -> bool {
1106 self.disable_pow
1107 }
1108
1109 pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1112 self.should_allow_unshielded_coinbase_spends
1113 }
1114
1115 pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
1117 self.pre_blossom_halving_interval
1118 }
1119
1120 pub fn post_blossom_halving_interval(&self) -> HeightDiff {
1122 self.post_blossom_halving_interval
1123 }
1124
1125 pub fn lockbox_disbursement_total_amount(&self) -> Amount<NonNegative> {
1127 self.lockbox_disbursements()
1128 .into_iter()
1129 .map(|(_addr, amount)| amount)
1130 .reduce(|a, b| (a + b).expect("sum of configured amounts should be valid"))
1131 .unwrap_or_default()
1132 }
1133
1134 pub fn lockbox_disbursements(&self) -> Vec<(transparent::Address, Amount<NonNegative>)> {
1136 self.lockbox_disbursements
1137 .iter()
1138 .map(|(addr, amount)| {
1139 (
1140 addr.parse().expect("hard-coded address must deserialize"),
1141 *amount,
1142 )
1143 })
1144 .collect()
1145 }
1146
1147 pub fn checkpoints(&self) -> Arc<CheckpointList> {
1149 self.checkpoints.clone()
1150 }
1151}
1152
1153impl Network {
1154 pub fn parameters(&self) -> Option<Arc<Parameters>> {
1156 if let Self::Testnet(parameters) = self {
1157 Some(parameters.clone())
1158 } else {
1159 None
1160 }
1161 }
1162
1163 pub fn disable_pow(&self) -> bool {
1165 if let Self::Testnet(params) = self {
1166 params.disable_pow()
1167 } else {
1168 false
1169 }
1170 }
1171
1172 pub fn slow_start_interval(&self) -> Height {
1174 if let Self::Testnet(params) = self {
1175 params.slow_start_interval()
1176 } else {
1177 SLOW_START_INTERVAL
1178 }
1179 }
1180
1181 pub fn slow_start_shift(&self) -> Height {
1183 if let Self::Testnet(params) = self {
1184 params.slow_start_shift()
1185 } else {
1186 SLOW_START_SHIFT
1187 }
1188 }
1189
1190 pub fn funding_streams(&self, height: Height) -> Option<&FundingStreams> {
1192 self.all_funding_streams()
1193 .iter()
1194 .find(|&streams| streams.height_range().contains(&height))
1195 }
1196
1197 pub fn all_funding_streams(&self) -> &Vec<FundingStreams> {
1199 if let Self::Testnet(params) = self {
1200 params.funding_streams()
1201 } else {
1202 &mainnet::FUNDING_STREAMS
1203 }
1204 }
1205
1206 pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1209 if let Self::Testnet(params) = self {
1210 params.should_allow_unshielded_coinbase_spends()
1211 } else {
1212 false
1213 }
1214 }
1215}