1use 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
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 into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) {
74 (
75 self.receiver,
76 FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()),
77 )
78 }
79}
80
81#[derive(Serialize, Deserialize, Clone, Debug)]
83#[serde(deny_unknown_fields)]
84pub struct ConfiguredLockboxDisbursement {
85 pub address: String,
87 pub amount: Amount<NonNegative>,
89}
90
91#[derive(Serialize, Deserialize, Clone, Default, Debug)]
93#[serde(deny_unknown_fields)]
94pub struct ConfiguredFundingStreams {
95 pub height_range: Option<std::ops::Range<Height>>,
97 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 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 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 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
251fn 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
268fn 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 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#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq)]
302#[serde(rename_all = "PascalCase", deny_unknown_fields)]
303pub struct ConfiguredActivationHeights {
304 pub before_overwinter: Option<u32>,
306 pub overwinter: Option<u32>,
308 pub sapling: Option<u32>,
310 pub blossom: Option<u32>,
312 pub heartwood: Option<u32>,
314 pub canopy: Option<u32>,
316 #[serde(rename = "NU5")]
318 pub nu5: Option<u32>,
319 #[serde(rename = "NU6")]
321 pub nu6: Option<u32>,
322 #[serde(rename = "NU6.1")]
324 pub nu6_1: Option<u32>,
325 #[serde(rename = "NU7")]
327 pub nu7: Option<u32>,
328 #[serde(rename = "ZFuture")]
330 #[cfg(zcash_unstable = "zfuture")]
331 pub zfuture: Option<u32>,
332}
333
334impl ConfiguredActivationHeights {
335 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#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
379#[serde(untagged)]
380pub enum ConfiguredCheckpoints {
381 Default(bool),
383 Path(std::path::PathBuf),
385 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#[derive(Clone, Debug, Eq, PartialEq)]
409pub struct ParametersBuilder {
410 network_name: String,
412 network_magic: Magic,
414 genesis_hash: block::Hash,
416 activation_heights: BTreeMap<Height, NetworkUpgrade>,
418 slow_start_interval: Height,
420 funding_streams: Vec<FundingStreams>,
422 should_lock_funding_stream_address_period: bool,
425 target_difficulty_limit: ExpandedDifficulty,
427 disable_pow: bool,
429 should_allow_unshielded_coinbase_spends: bool,
432 pre_blossom_halving_interval: HeightDiff,
434 post_blossom_halving_interval: HeightDiff,
436 lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
438 checkpoints: Arc<CheckpointList>,
440}
441
442impl Default for ParametersBuilder {
443 fn default() -> Self {
445 Self {
446 network_name: "UnknownTestnet".to_string(),
447 network_magic: magics::TESTNET,
448 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 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 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 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 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 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 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 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 self.activation_heights.split_off(&Height(1));
624 self.activation_heights.extend(activation_heights);
625
626 Ok(self)
627 }
628
629 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 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 pub fn clear_funding_streams(mut self) -> Self {
656 self.funding_streams = vec![];
657 self
658 }
659
660 pub fn extend_funding_streams(mut self) -> Self {
665 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 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 pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
697 self.disable_pow = disable_pow;
698 self
699 }
700
701 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 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 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 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 pub fn clear_checkpoints(self) -> Result<Self, ParametersBuilderError> {
773 self.with_checkpoints(ConfiguredCheckpoints::Default(false))
774 }
775
776 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 fn to_network_unchecked(&self) -> Network {
814 Network::new_configured_testnet(self.clone().finish())
815 }
816
817 pub fn to_network(self) -> Result<Network, ParametersBuilderError> {
819 let network = self.to_network_unchecked();
820
821 for fs in &self.funding_streams {
823 check_funding_stream_address_period(fs, &network);
825 }
826
827 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 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#[derive(Debug, Default, Clone)]
874pub struct RegtestParameters {
875 pub activation_heights: ConfiguredActivationHeights,
877 pub funding_streams: Option<Vec<ConfiguredFundingStreams>>,
879 pub lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
881 pub checkpoints: Option<ConfiguredCheckpoints>,
883 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#[derive(Clone, Debug, Eq, PartialEq)]
898pub struct Parameters {
899 network_name: String,
901 network_magic: Magic,
903 genesis_hash: block::Hash,
905 activation_heights: BTreeMap<Height, NetworkUpgrade>,
907 slow_start_interval: Height,
909 slow_start_shift: Height,
911 funding_streams: Vec<FundingStreams>,
913 target_difficulty_limit: ExpandedDifficulty,
915 disable_pow: bool,
917 should_allow_unshielded_coinbase_spends: bool,
920 pre_blossom_halving_interval: HeightDiff,
922 post_blossom_halving_interval: HeightDiff,
924 lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
926 checkpoints: Arc<CheckpointList>,
928}
929
930impl Default for Parameters {
931 fn default() -> Self {
933 Self {
934 network_name: "Testnet".to_string(),
935 ..Self::build().finish()
936 }
937 }
938}
939
940impl Parameters {
941 pub fn build() -> ParametersBuilder {
943 ParametersBuilder::default()
944 }
945
946 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 .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 .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 pub fn is_default_testnet(&self) -> bool {
986 self == &Self::default()
987 }
988
989 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 network_magic: _,
999 genesis_hash,
1000 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 pub fn network_name(&self) -> &str {
1028 &self.network_name
1029 }
1030
1031 pub fn network_magic(&self) -> Magic {
1033 self.network_magic
1034 }
1035
1036 pub fn genesis_hash(&self) -> block::Hash {
1038 self.genesis_hash
1039 }
1040
1041 pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
1043 &self.activation_heights
1044 }
1045
1046 pub fn slow_start_interval(&self) -> Height {
1048 self.slow_start_interval
1049 }
1050
1051 pub fn slow_start_shift(&self) -> Height {
1053 self.slow_start_shift
1054 }
1055
1056 pub fn funding_streams(&self) -> &Vec<FundingStreams> {
1058 &self.funding_streams
1059 }
1060
1061 pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
1063 self.target_difficulty_limit
1064 }
1065
1066 pub fn disable_pow(&self) -> bool {
1068 self.disable_pow
1069 }
1070
1071 pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1074 self.should_allow_unshielded_coinbase_spends
1075 }
1076
1077 pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
1079 self.pre_blossom_halving_interval
1080 }
1081
1082 pub fn post_blossom_halving_interval(&self) -> HeightDiff {
1084 self.post_blossom_halving_interval
1085 }
1086
1087 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 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 pub fn checkpoints(&self) -> Arc<CheckpointList> {
1111 self.checkpoints.clone()
1112 }
1113}
1114
1115impl Network {
1116 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 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 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 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 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 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 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}