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_CHECKPOINT_LIST},
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::Nu6_2 => &mut configured_activation_heights.nu6_2,
193 NetworkUpgrade::Nu6_3 => &mut configured_activation_heights.nu6_3,
194 NetworkUpgrade::Nu7 => &mut configured_activation_heights.nu7,
195 #[cfg(zcash_unstable = "zfuture")]
196 NetworkUpgrade::ZFuture => &mut configured_activation_heights.zfuture,
197 NetworkUpgrade::Genesis => continue,
198 };
199
200 *field = Some(height.0)
201 }
202
203 configured_activation_heights
204 }
205}
206
207impl From<BTreeMap<Height, NetworkUpgrade>> for ConfiguredActivationHeights {
208 fn from(value: BTreeMap<Height, NetworkUpgrade>) -> Self {
209 Self::from(&value)
210 }
211}
212
213impl ConfiguredFundingStreams {
214 fn convert_with_default(
221 self,
222 default_funding_streams: Option<FundingStreams>,
223 ) -> FundingStreams {
224 let height_range = self.height_range.unwrap_or_else(|| {
225 default_funding_streams
226 .as_ref()
227 .expect("default required")
228 .height_range()
229 .clone()
230 });
231
232 let recipients = self
233 .recipients
234 .map(|recipients| {
235 recipients
236 .into_iter()
237 .map(ConfiguredFundingStreamRecipient::into_recipient)
238 .collect()
239 })
240 .unwrap_or_else(|| {
241 default_funding_streams
242 .as_ref()
243 .expect("default required")
244 .recipients()
245 .clone()
246 });
247
248 assert!(
249 height_range.start <= height_range.end,
250 "funding stream end height must be above start height"
251 );
252
253 let funding_streams = FundingStreams::new(height_range.clone(), recipients);
254
255 let sum_numerators: u64 = funding_streams
258 .recipients()
259 .values()
260 .map(|r| r.numerator())
261 .sum();
262
263 assert!(
264 sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR,
265 "sum of funding stream numerators must not be \
266 greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}"
267 );
268
269 funding_streams
270 }
271
272 pub fn into_funding_streams_unchecked(self) -> FundingStreams {
278 let height_range = self.height_range.expect("must have height range");
279 let recipients = self
280 .recipients
281 .into_iter()
282 .flat_map(|recipients| {
283 recipients
284 .into_iter()
285 .map(ConfiguredFundingStreamRecipient::into_recipient)
286 })
287 .collect();
288
289 FundingStreams::new(height_range, recipients)
290 }
291}
292
293fn num_funding_stream_addresses_required_for_height_range(
295 height_range: &std::ops::Range<Height>,
296 network: &Network,
297) -> usize {
298 1u32.checked_add(funding_stream_address_period(
299 height_range
300 .end
301 .previous()
302 .expect("end height must be above start height and genesis height"),
303 network,
304 ))
305 .expect("no overflow should happen in this sum")
306 .checked_sub(funding_stream_address_period(height_range.start, network))
307 .expect("no overflow should happen in this sub") as usize
308}
309
310fn check_funding_stream_address_period(funding_streams: &FundingStreams, network: &Network) {
313 let expected_min_num_addresses = num_funding_stream_addresses_required_for_height_range(
314 funding_streams.height_range(),
315 network,
316 );
317
318 for (&receiver, recipient) in funding_streams.recipients() {
319 if receiver == FundingStreamReceiver::Deferred {
320 continue;
322 }
323
324 let num_addresses = recipient.addresses().len();
325 assert!(
326 num_addresses >= expected_min_num_addresses,
327 "recipients must have a sufficient number of addresses for height range, \
328 minimum num addresses required: {expected_min_num_addresses}, only {num_addresses} were provided.\
329 receiver: {receiver:?}, recipient: {recipient:?}"
330 );
331
332 for address in recipient.addresses() {
333 assert_eq!(
334 address.network_kind(),
335 NetworkKind::Testnet,
336 "configured funding stream addresses must be for Testnet"
337 );
338 }
339 }
340}
341
342#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq)]
344#[serde(rename_all = "PascalCase", deny_unknown_fields)]
345pub struct ConfiguredActivationHeights {
346 pub before_overwinter: Option<u32>,
348 pub overwinter: Option<u32>,
350 pub sapling: Option<u32>,
352 pub blossom: Option<u32>,
354 pub heartwood: Option<u32>,
356 pub canopy: Option<u32>,
358 #[serde(rename = "NU5")]
360 pub nu5: Option<u32>,
361 #[serde(rename = "NU6")]
363 pub nu6: Option<u32>,
364 #[serde(rename = "NU6.1")]
366 pub nu6_1: Option<u32>,
367 #[serde(rename = "NU6.2")]
369 pub nu6_2: Option<u32>,
370 #[serde(rename = "NU6.3")]
372 pub nu6_3: Option<u32>,
373 #[serde(rename = "NU7")]
375 pub nu7: Option<u32>,
376 #[serde(rename = "ZFuture")]
378 #[cfg(zcash_unstable = "zfuture")]
379 pub zfuture: Option<u32>,
380}
381
382impl ConfiguredActivationHeights {
383 fn for_regtest(self) -> Self {
386 let Self {
387 before_overwinter,
388 overwinter,
389 sapling,
390 blossom,
391 heartwood,
392 canopy,
393 nu5,
394 nu6,
395 nu6_1,
396 nu6_2,
397 nu6_3,
398 nu7,
399 #[cfg(zcash_unstable = "zfuture")]
400 zfuture,
401 } = self;
402
403 let overwinter = overwinter.or(before_overwinter).or(Some(1));
404 let sapling = sapling.or(overwinter);
405 let blossom = blossom.or(sapling);
406 let heartwood = heartwood.or(blossom);
407 let canopy = canopy.or(heartwood);
408
409 Self {
410 before_overwinter,
411 overwinter,
412 sapling,
413 blossom,
414 heartwood,
415 canopy,
416 nu5,
417 nu6,
418 nu6_1,
419 nu6_2,
420 nu6_3,
421 nu7,
422 #[cfg(zcash_unstable = "zfuture")]
423 zfuture,
424 }
425 }
426}
427
428#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
431#[serde(untagged)]
432pub enum ConfiguredCheckpoints {
433 Default(bool),
435 Path(std::path::PathBuf),
437 HeightsAndHashes(Vec<(block::Height, block::Hash)>),
439}
440
441impl Default for ConfiguredCheckpoints {
442 fn default() -> Self {
443 Self::Default(false)
444 }
445}
446
447impl From<Arc<CheckpointList>> for ConfiguredCheckpoints {
448 fn from(value: Arc<CheckpointList>) -> Self {
449 Self::HeightsAndHashes(value.iter_cloned().collect())
450 }
451}
452
453impl From<bool> for ConfiguredCheckpoints {
454 fn from(value: bool) -> Self {
455 Self::Default(value)
456 }
457}
458
459#[derive(Clone, Debug, Eq, PartialEq)]
461pub struct ParametersBuilder {
462 network_name: String,
464 network_magic: Magic,
466 genesis_hash: block::Hash,
468 activation_heights: BTreeMap<Height, NetworkUpgrade>,
470 slow_start_interval: Height,
472 funding_streams: Vec<FundingStreams>,
474 should_lock_funding_stream_address_period: bool,
477 target_difficulty_limit: ExpandedDifficulty,
479 disable_pow: bool,
481 should_allow_unshielded_coinbase_spends: bool,
484 pre_blossom_halving_interval: HeightDiff,
486 post_blossom_halving_interval: HeightDiff,
488 lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
490 checkpoints: Arc<CheckpointList>,
492 temporary_orchard_disabling_soft_fork_height: Option<Height>,
494}
495
496impl Default for ParametersBuilder {
497 fn default() -> Self {
499 Self {
500 network_name: "UnknownTestnet".to_string(),
501 network_magic: magics::TESTNET,
502 activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
506 genesis_hash: TESTNET_GENESIS_HASH
507 .parse()
508 .expect("hard-coded hash parses"),
509 slow_start_interval: SLOW_START_INTERVAL,
510 target_difficulty_limit: ExpandedDifficulty::from((U256::one() << 251) - 1)
516 .to_compact()
517 .to_expanded()
518 .expect("difficulty limits are valid expanded values"),
519 disable_pow: false,
520 funding_streams: testnet::FUNDING_STREAMS.clone(),
521 should_lock_funding_stream_address_period: false,
522 pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL,
523 post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL,
524 should_allow_unshielded_coinbase_spends: false,
525 lockbox_disbursements: testnet::NU6_1_LOCKBOX_DISBURSEMENTS
526 .iter()
527 .map(|(addr, amount)| (addr.to_string(), *amount))
528 .collect(),
529 checkpoints: TESTNET_CHECKPOINT_LIST.clone(),
530 temporary_orchard_disabling_soft_fork_height: Some(
531 super::TESTNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT,
532 ),
533 }
534 }
535}
536
537impl ParametersBuilder {
538 pub fn with_network_name(
540 mut self,
541 network_name: impl fmt::Display,
542 ) -> Result<Self, ParametersBuilderError> {
543 let network_name = network_name.to_string();
544
545 if RESERVED_NETWORK_NAMES.contains(&network_name.as_str()) {
546 return Err(ParametersBuilderError::ReservedNetworkName {
547 network_name,
548 reserved_names: RESERVED_NETWORK_NAMES.to_vec(),
549 });
550 }
551
552 if network_name.len() > MAX_NETWORK_NAME_LENGTH {
553 return Err(ParametersBuilderError::NetworkNameTooLong {
554 network_name,
555 max_length: MAX_NETWORK_NAME_LENGTH,
556 });
557 }
558
559 if !network_name
560 .chars()
561 .all(|x| x.is_alphanumeric() || x == '_')
562 {
563 return Err(ParametersBuilderError::InvalidCharacter);
564 }
565
566 self.network_name = network_name;
567 Ok(self)
568 }
569
570 pub fn with_network_magic(
572 mut self,
573 network_magic: Magic,
574 ) -> Result<Self, ParametersBuilderError> {
575 if [magics::MAINNET, magics::REGTEST]
576 .into_iter()
577 .any(|reserved_magic| network_magic == reserved_magic)
578 {
579 return Err(ParametersBuilderError::ReservedNetworkMagic);
580 }
581
582 self.network_magic = network_magic;
583
584 Ok(self)
585 }
586
587 pub fn with_genesis_hash(
589 mut self,
590 genesis_hash: impl fmt::Display,
591 ) -> Result<Self, ParametersBuilderError> {
592 self.genesis_hash = genesis_hash
593 .to_string()
594 .parse()
595 .map_err(|_| ParametersBuilderError::InvalidGenesisHash)?;
596 Ok(self)
597 }
598
599 pub fn with_activation_heights(
602 mut self,
603 ConfiguredActivationHeights {
604 before_overwinter,
605 overwinter,
606 sapling,
607 blossom,
608 heartwood,
609 canopy,
610 nu5,
611 nu6,
612 nu6_1,
613 nu6_2,
614 nu6_3,
615 nu7,
616 #[cfg(zcash_unstable = "zfuture")]
617 zfuture,
618 }: ConfiguredActivationHeights,
619 ) -> Result<Self, ParametersBuilderError> {
620 use NetworkUpgrade::*;
621
622 if self.should_lock_funding_stream_address_period {
623 return Err(ParametersBuilderError::LockFundingStreams);
624 }
625
626 let activation_heights: BTreeMap<_, _> = {
631 let activation_heights = before_overwinter
632 .into_iter()
633 .map(|h| (h, BeforeOverwinter))
634 .chain(overwinter.into_iter().map(|h| (h, Overwinter)))
635 .chain(sapling.into_iter().map(|h| (h, Sapling)))
636 .chain(blossom.into_iter().map(|h| (h, Blossom)))
637 .chain(heartwood.into_iter().map(|h| (h, Heartwood)))
638 .chain(canopy.into_iter().map(|h| (h, Canopy)))
639 .chain(nu5.into_iter().map(|h| (h, Nu5)))
640 .chain(nu6.into_iter().map(|h| (h, Nu6)))
641 .chain(nu6_1.into_iter().map(|h| (h, Nu6_1)))
642 .chain(nu6_2.into_iter().map(|h| (h, Nu6_2)))
643 .chain(nu6_3.into_iter().map(|h| (h, Nu6_3)))
644 .chain(nu7.into_iter().map(|h| (h, Nu7)));
645
646 #[cfg(zcash_unstable = "zfuture")]
647 let activation_heights =
648 activation_heights.chain(zfuture.into_iter().map(|h| (h, ZFuture)));
649
650 activation_heights
651 .map(|(h, nu)| {
652 let height = h
653 .try_into()
654 .map_err(|_| ParametersBuilderError::InvalidActivationHeight)?;
655 Ok((height, nu))
656 })
657 .collect::<Result<BTreeMap<_, _>, _>>()?
658 };
659
660 let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect();
661
662 let mut activation_heights_iter = activation_heights.iter();
664 for expected_network_upgrade in NetworkUpgrade::iter() {
665 if !network_upgrades.contains(&expected_network_upgrade) {
666 continue;
667 } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
668 if height == Height(0) {
669 return Err(ParametersBuilderError::InvalidHeightZero);
670 }
671
672 if network_upgrade != expected_network_upgrade {
673 return Err(ParametersBuilderError::OutOfOrderUpgrades);
674 }
675 }
676 }
677
678 self.activation_heights.split_off(&Height(1));
682 self.activation_heights.extend(activation_heights);
683
684 Ok(self)
685 }
686
687 pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
689 self.slow_start_interval = slow_start_interval;
690 self
691 }
692
693 pub fn with_funding_streams(mut self, funding_streams: Vec<ConfiguredFundingStreams>) -> Self {
700 self.funding_streams = funding_streams
701 .into_iter()
702 .enumerate()
703 .map(|(idx, streams)| {
704 let default_streams = testnet::FUNDING_STREAMS.get(idx).cloned();
705 streams.convert_with_default(default_streams)
706 })
707 .collect();
708 self.should_lock_funding_stream_address_period = true;
709 self
710 }
711
712 pub fn clear_funding_streams(mut self) -> Self {
714 self.funding_streams = vec![];
715 self
716 }
717
718 pub fn extend_funding_streams(mut self) -> Self {
723 let network = self.to_network_unchecked();
724
725 for funding_streams in &mut self.funding_streams {
726 funding_streams.extend_recipient_addresses(
727 num_funding_stream_addresses_required_for_height_range(
728 funding_streams.height_range(),
729 &network,
730 ),
731 );
732 }
733
734 self
735 }
736
737 pub fn with_target_difficulty_limit(
740 mut self,
741 target_difficulty_limit: impl Into<ExpandedDifficulty>,
742 ) -> Result<Self, ParametersBuilderError> {
743 self.target_difficulty_limit = target_difficulty_limit
744 .into()
745 .to_compact()
746 .to_expanded()
747 .ok_or(ParametersBuilderError::InvaildDifficultyLimits)?;
748 Ok(self)
749 }
750
751 pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
753 self.disable_pow = disable_pow;
754 self
755 }
756
757 pub fn with_unshielded_coinbase_spends(
760 mut self,
761 should_allow_unshielded_coinbase_spends: bool,
762 ) -> Self {
763 self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends;
764 self
765 }
766
767 pub fn with_halving_interval(
769 mut self,
770 pre_blossom_halving_interval: HeightDiff,
771 ) -> Result<Self, ParametersBuilderError> {
772 if self.should_lock_funding_stream_address_period {
773 return Err(ParametersBuilderError::HalvingIntervalAfterFundingStreams);
774 }
775
776 self.pre_blossom_halving_interval = pre_blossom_halving_interval;
777 self.post_blossom_halving_interval =
778 self.pre_blossom_halving_interval * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
779 Ok(self)
780 }
781
782 pub fn with_lockbox_disbursements(
784 mut self,
785 lockbox_disbursements: Vec<ConfiguredLockboxDisbursement>,
786 ) -> Self {
787 self.lockbox_disbursements = lockbox_disbursements
788 .into_iter()
789 .map(|ConfiguredLockboxDisbursement { address, amount }| (address, amount))
790 .collect();
791 self
792 }
793
794 pub fn with_checkpoints(
796 mut self,
797 checkpoints: impl Into<ConfiguredCheckpoints>,
798 ) -> Result<Self, ParametersBuilderError> {
799 self.checkpoints = match checkpoints.into() {
800 ConfiguredCheckpoints::Default(true) => TESTNET_CHECKPOINT_LIST.clone(),
801 ConfiguredCheckpoints::Default(false) => Arc::new(
802 CheckpointList::from_list([(block::Height(0), self.genesis_hash)])
803 .map_err(|_| ParametersBuilderError::FailedToParseDefaultCheckpoint)?,
804 ),
805 ConfiguredCheckpoints::Path(path_buf) => {
806 let Ok(raw_checkpoints_str) = std::fs::read_to_string(&path_buf) else {
807 return Err(ParametersBuilderError::FailedToReadCheckpointFile {
808 path_buf: path_buf.clone(),
809 });
810 };
811
812 Arc::new(
813 raw_checkpoints_str
814 .parse::<CheckpointList>()
815 .map_err(|err| ParametersBuilderError::FailedToParseCheckpointFile {
816 path_buf: path_buf.clone(),
817 err: err.to_string(),
818 })?,
819 )
820 }
821 ConfiguredCheckpoints::HeightsAndHashes(items) => Arc::new(
822 CheckpointList::from_list(items)
823 .map_err(|_| ParametersBuilderError::InvalidCustomCheckpoints)?,
824 ),
825 };
826
827 Ok(self)
828 }
829
830 pub fn clear_checkpoints(self) -> Result<Self, ParametersBuilderError> {
832 self.with_checkpoints(ConfiguredCheckpoints::Default(false))
833 }
834
835 pub fn with_temporary_orchard_disabling_soft_fork_height(mut self, height: Height) -> Self {
838 self.temporary_orchard_disabling_soft_fork_height = Some(height);
839 self
840 }
841
842 pub fn disable_temporary_orchard_disabling_soft_fork(mut self) -> Self {
844 self.temporary_orchard_disabling_soft_fork_height = None;
845 self
846 }
847
848 fn finish(self) -> Parameters {
850 let Self {
851 network_name,
852 network_magic,
853 genesis_hash,
854 activation_heights,
855 slow_start_interval,
856 funding_streams,
857 should_lock_funding_stream_address_period: _,
858 target_difficulty_limit,
859 disable_pow,
860 should_allow_unshielded_coinbase_spends,
861 pre_blossom_halving_interval,
862 post_blossom_halving_interval,
863 lockbox_disbursements,
864 checkpoints,
865 temporary_orchard_disabling_soft_fork_height,
866 } = self;
867 Parameters {
868 network_name,
869 network_magic,
870 genesis_hash,
871 activation_heights,
872 slow_start_interval,
873 slow_start_shift: Height(slow_start_interval.0 / 2),
874 funding_streams,
875 target_difficulty_limit,
876 disable_pow,
877 should_allow_unshielded_coinbase_spends,
878 pre_blossom_halving_interval,
879 post_blossom_halving_interval,
880 lockbox_disbursements,
881 checkpoints,
882 temporary_orchard_disabling_soft_fork_height,
883 }
884 }
885
886 fn to_network_unchecked(&self) -> Network {
888 Network::new_configured_testnet(self.clone().finish())
889 }
890
891 pub fn to_network(self) -> Result<Network, ParametersBuilderError> {
893 let network = self.to_network_unchecked();
894
895 for fs in &self.funding_streams {
897 check_funding_stream_address_period(fs, &network);
899 }
900
901 if network.checkpoint_list().hash(Height(0)) != Some(network.genesis_hash()) {
903 return Err(ParametersBuilderError::CheckpointGenesisMismatch);
904 }
905 if network.checkpoint_list().max_height() < network.mandatory_checkpoint_height() {
906 return Err(ParametersBuilderError::InsufficientCheckpointCoverage);
907 }
908
909 Ok(network)
910 }
911
912 pub fn is_compatible_with_default_parameters(&self) -> bool {
914 let Self {
915 network_name: _,
916 network_magic,
917 genesis_hash,
918 activation_heights,
919 slow_start_interval,
920 funding_streams,
921 should_lock_funding_stream_address_period: _,
922 target_difficulty_limit,
923 disable_pow,
924 should_allow_unshielded_coinbase_spends,
925 pre_blossom_halving_interval,
926 post_blossom_halving_interval,
927 lockbox_disbursements,
928 checkpoints: _,
929 temporary_orchard_disabling_soft_fork_height: _,
930 } = Self::default();
931
932 self.activation_heights == activation_heights
933 && self.network_magic == network_magic
934 && self.genesis_hash == genesis_hash
935 && self.slow_start_interval == slow_start_interval
936 && self.funding_streams == funding_streams
937 && self.target_difficulty_limit == target_difficulty_limit
938 && self.disable_pow == disable_pow
939 && self.should_allow_unshielded_coinbase_spends
940 == should_allow_unshielded_coinbase_spends
941 && self.pre_blossom_halving_interval == pre_blossom_halving_interval
942 && self.post_blossom_halving_interval == post_blossom_halving_interval
943 && self.lockbox_disbursements == lockbox_disbursements
944 }
945}
946
947#[derive(Debug, Default, Clone)]
949pub struct RegtestParameters {
950 pub activation_heights: ConfiguredActivationHeights,
952 pub funding_streams: Option<Vec<ConfiguredFundingStreams>>,
954 pub lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
956 pub checkpoints: Option<ConfiguredCheckpoints>,
958 pub extend_funding_stream_addresses_as_required: Option<bool>,
960 pub should_allow_unshielded_coinbase_spends: Option<bool>,
963}
964
965impl From<ConfiguredActivationHeights> for RegtestParameters {
966 fn from(value: ConfiguredActivationHeights) -> Self {
967 Self {
968 activation_heights: value,
969 ..Default::default()
970 }
971 }
972}
973
974#[derive(Clone, Debug, Eq, PartialEq)]
976pub struct Parameters {
977 network_name: String,
979 network_magic: Magic,
981 genesis_hash: block::Hash,
983 activation_heights: BTreeMap<Height, NetworkUpgrade>,
985 slow_start_interval: Height,
987 slow_start_shift: Height,
989 funding_streams: Vec<FundingStreams>,
991 target_difficulty_limit: ExpandedDifficulty,
993 disable_pow: bool,
995 should_allow_unshielded_coinbase_spends: bool,
998 pre_blossom_halving_interval: HeightDiff,
1000 post_blossom_halving_interval: HeightDiff,
1002 lockbox_disbursements: Vec<(String, Amount<NonNegative>)>,
1004 checkpoints: Arc<CheckpointList>,
1006 temporary_orchard_disabling_soft_fork_height: Option<Height>,
1008}
1009
1010impl Default for Parameters {
1011 fn default() -> Self {
1013 Self {
1014 network_name: "Testnet".to_string(),
1015 ..Self::build().finish()
1016 }
1017 }
1018}
1019
1020impl Parameters {
1021 pub fn build() -> ParametersBuilder {
1023 ParametersBuilder::default()
1024 }
1025
1026 pub fn new_regtest(
1030 RegtestParameters {
1031 activation_heights,
1032 funding_streams,
1033 lockbox_disbursements,
1034 checkpoints,
1035 extend_funding_stream_addresses_as_required,
1036 should_allow_unshielded_coinbase_spends,
1037 }: RegtestParameters,
1038 ) -> Result<Self, ParametersBuilderError> {
1039 let mut parameters = Self::build()
1040 .with_genesis_hash(REGTEST_GENESIS_HASH)?
1041 .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))?
1043 .with_disable_pow(true)
1044 .with_unshielded_coinbase_spends(
1045 should_allow_unshielded_coinbase_spends.unwrap_or(true),
1046 )
1047 .with_slow_start_interval(Height::MIN)
1048 .disable_temporary_orchard_disabling_soft_fork()
1051 .with_activation_heights(activation_heights.for_regtest())?
1054 .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL)?
1055 .with_funding_streams(funding_streams.unwrap_or_default())
1056 .with_lockbox_disbursements(lockbox_disbursements.unwrap_or_default())
1057 .with_checkpoints(checkpoints.unwrap_or_default())?;
1058
1059 if Some(true) == extend_funding_stream_addresses_as_required {
1060 parameters = parameters.extend_funding_streams();
1061 }
1062
1063 Ok(Self {
1064 network_name: "Regtest".to_string(),
1065 network_magic: magics::REGTEST,
1066 ..parameters.finish()
1067 })
1068 }
1069
1070 pub fn is_default_testnet(&self) -> bool {
1072 self == &Self::default()
1073 }
1074
1075 pub fn is_regtest(&self) -> bool {
1077 if self.network_magic != magics::REGTEST {
1078 return false;
1079 }
1080
1081 let Self {
1082 network_name,
1083 network_magic: _,
1085 genesis_hash,
1086 activation_heights: _,
1088 slow_start_interval,
1089 slow_start_shift,
1090 funding_streams: _,
1091 target_difficulty_limit,
1092 disable_pow,
1093 should_allow_unshielded_coinbase_spends: _,
1095 pre_blossom_halving_interval,
1096 post_blossom_halving_interval,
1097 lockbox_disbursements: _,
1098 checkpoints: _,
1099 temporary_orchard_disabling_soft_fork_height: _,
1100 } = Self::new_regtest(Default::default()).expect("default regtest parameters are valid");
1101
1102 self.network_name == network_name
1103 && self.genesis_hash == genesis_hash
1104 && self.slow_start_interval == slow_start_interval
1105 && self.slow_start_shift == slow_start_shift
1106 && self.target_difficulty_limit == target_difficulty_limit
1107 && self.disable_pow == disable_pow
1108 && self.pre_blossom_halving_interval == pre_blossom_halving_interval
1109 && self.post_blossom_halving_interval == post_blossom_halving_interval
1110 }
1111
1112 pub fn network_name(&self) -> &str {
1114 &self.network_name
1115 }
1116
1117 pub fn network_magic(&self) -> Magic {
1119 self.network_magic
1120 }
1121
1122 pub fn genesis_hash(&self) -> block::Hash {
1124 self.genesis_hash
1125 }
1126
1127 pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
1129 &self.activation_heights
1130 }
1131
1132 pub fn slow_start_interval(&self) -> Height {
1134 self.slow_start_interval
1135 }
1136
1137 pub fn slow_start_shift(&self) -> Height {
1139 self.slow_start_shift
1140 }
1141
1142 pub fn funding_streams(&self) -> &Vec<FundingStreams> {
1144 &self.funding_streams
1145 }
1146
1147 pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
1149 self.target_difficulty_limit
1150 }
1151
1152 pub fn disable_pow(&self) -> bool {
1154 self.disable_pow
1155 }
1156
1157 pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1160 self.should_allow_unshielded_coinbase_spends
1161 }
1162
1163 pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
1165 self.pre_blossom_halving_interval
1166 }
1167
1168 pub fn post_blossom_halving_interval(&self) -> HeightDiff {
1170 self.post_blossom_halving_interval
1171 }
1172
1173 pub fn lockbox_disbursement_total_amount(&self) -> Amount<NonNegative> {
1175 self.lockbox_disbursements()
1176 .into_iter()
1177 .map(|(_addr, amount)| amount)
1178 .reduce(|a, b| (a + b).expect("sum of configured amounts should be valid"))
1179 .unwrap_or_default()
1180 }
1181
1182 pub fn lockbox_disbursements(&self) -> Vec<(transparent::Address, Amount<NonNegative>)> {
1184 self.lockbox_disbursements
1185 .iter()
1186 .map(|(addr, amount)| {
1187 (
1188 addr.parse().expect("hard-coded address must deserialize"),
1189 *amount,
1190 )
1191 })
1192 .collect()
1193 }
1194
1195 pub fn checkpoints(&self) -> Arc<CheckpointList> {
1197 self.checkpoints.clone()
1198 }
1199
1200 pub fn temporary_orchard_disabling_soft_fork_height(&self) -> Option<Height> {
1203 self.temporary_orchard_disabling_soft_fork_height
1204 }
1205}
1206
1207impl Network {
1208 pub fn parameters(&self) -> Option<Arc<Parameters>> {
1210 if let Self::Testnet(parameters) = self {
1211 Some(parameters.clone())
1212 } else {
1213 None
1214 }
1215 }
1216
1217 pub fn disable_pow(&self) -> bool {
1219 if let Self::Testnet(params) = self {
1220 params.disable_pow()
1221 } else {
1222 false
1223 }
1224 }
1225
1226 pub fn slow_start_interval(&self) -> Height {
1228 if let Self::Testnet(params) = self {
1229 params.slow_start_interval()
1230 } else {
1231 SLOW_START_INTERVAL
1232 }
1233 }
1234
1235 pub fn slow_start_shift(&self) -> Height {
1237 if let Self::Testnet(params) = self {
1238 params.slow_start_shift()
1239 } else {
1240 SLOW_START_SHIFT
1241 }
1242 }
1243
1244 pub fn funding_streams(&self, height: Height) -> Option<&FundingStreams> {
1246 self.all_funding_streams()
1247 .iter()
1248 .find(|&streams| streams.height_range().contains(&height))
1249 }
1250
1251 pub fn all_funding_streams(&self) -> &Vec<FundingStreams> {
1253 if let Self::Testnet(params) = self {
1254 params.funding_streams()
1255 } else {
1256 &mainnet::FUNDING_STREAMS
1257 }
1258 }
1259
1260 pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
1263 if let Self::Testnet(params) = self {
1264 params.should_allow_unshielded_coinbase_spends()
1265 } else {
1266 false
1267 }
1268 }
1269
1270 pub fn founder_address_list(&self) -> &[&str] {
1272 match self {
1273 Network::Mainnet => &mainnet::FOUNDER_ADDRESS_LIST,
1274 Network::Testnet(_) => &testnet::FOUNDER_ADDRESS_LIST,
1275 }
1276 }
1277}