1use std::{collections::BTreeMap, fmt};
3
4use crate::{
5 block::{self, Height, HeightDiff},
6 parameters::{
7 constants::{magics, SLOW_START_INTERVAL, SLOW_START_SHIFT},
8 network_upgrade::TESTNET_ACTIVATION_HEIGHTS,
9 subsidy::{funding_stream_address_period, FUNDING_STREAM_RECEIVER_DENOMINATOR},
10 Network, NetworkKind, NetworkUpgrade,
11 },
12 work::difficulty::{ExpandedDifficulty, U256},
13};
14
15use super::{
16 magic::Magic,
17 subsidy::{
18 FundingStreamReceiver, FundingStreamRecipient, FundingStreams,
19 BLOSSOM_POW_TARGET_SPACING_RATIO, POST_BLOSSOM_HALVING_INTERVAL,
20 POST_NU6_FUNDING_STREAMS_MAINNET, POST_NU6_FUNDING_STREAMS_TESTNET,
21 PRE_BLOSSOM_HALVING_INTERVAL, PRE_NU6_FUNDING_STREAMS_MAINNET,
22 PRE_NU6_FUNDING_STREAMS_TESTNET,
23 },
24};
25
26#[cfg(any(test, feature = "proptest-impl"))]
29pub const REGTEST_NU5_ACTIVATION_HEIGHT: u32 = 100;
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(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(Deserialize, Clone, Default, Debug)]
83#[serde(deny_unknown_fields)]
84pub struct ConfiguredFundingStreams {
85 pub height_range: Option<std::ops::Range<Height>>,
87 pub recipients: Option<Vec<ConfiguredFundingStreamRecipient>>,
89}
90
91impl ConfiguredFundingStreams {
92 fn empty() -> Self {
94 Self {
95 height_range: None,
96 recipients: Some(Vec::new()),
97 }
98 }
99
100 fn convert_with_default(
103 self,
104 default_funding_streams: FundingStreams,
105 parameters_builder: &ParametersBuilder,
106 ) -> FundingStreams {
107 let network = parameters_builder.to_network_unchecked();
108 let height_range = self
109 .height_range
110 .unwrap_or(default_funding_streams.height_range().clone());
111
112 let recipients = self
113 .recipients
114 .map(|recipients| {
115 recipients
116 .into_iter()
117 .map(ConfiguredFundingStreamRecipient::into_recipient)
118 .collect()
119 })
120 .unwrap_or(default_funding_streams.recipients().clone());
121
122 assert!(
123 height_range.start < height_range.end,
124 "funding stream end height must be above start height"
125 );
126
127 let funding_streams = FundingStreams::new(height_range.clone(), recipients);
128
129 check_funding_stream_address_period(&funding_streams, &network);
130
131 let sum_numerators: u64 = funding_streams
134 .recipients()
135 .values()
136 .map(|r| r.numerator())
137 .sum();
138
139 assert!(
140 sum_numerators <= FUNDING_STREAM_RECEIVER_DENOMINATOR,
141 "sum of funding stream numerators must not be \
142 greater than denominator of {FUNDING_STREAM_RECEIVER_DENOMINATOR}"
143 );
144
145 funding_streams
146 }
147}
148
149fn check_funding_stream_address_period(funding_streams: &FundingStreams, network: &Network) {
152 let height_range = funding_streams.height_range();
153 let expected_min_num_addresses =
154 1u32.checked_add(funding_stream_address_period(
155 height_range
156 .end
157 .previous()
158 .expect("end height must be above start height and genesis height"),
159 network,
160 ))
161 .expect("no overflow should happen in this sum")
162 .checked_sub(funding_stream_address_period(height_range.start, network))
163 .expect("no overflow should happen in this sub") as usize;
164
165 for (&receiver, recipient) in funding_streams.recipients() {
166 if receiver == FundingStreamReceiver::Deferred {
167 continue;
169 }
170
171 assert!(
172 recipient.addresses().len() >= expected_min_num_addresses,
173 "recipients must have a sufficient number of addresses for height range, \
174 minimum num addresses required: {expected_min_num_addresses}"
175 );
176
177 for address in recipient.addresses() {
178 assert_eq!(
179 address.network_kind(),
180 NetworkKind::Testnet,
181 "configured funding stream addresses must be for Testnet"
182 );
183 }
184 }
185}
186
187#[derive(Deserialize, Default, Clone)]
189#[serde(rename_all = "PascalCase", deny_unknown_fields)]
190pub struct ConfiguredActivationHeights {
191 pub before_overwinter: Option<u32>,
193 pub overwinter: Option<u32>,
195 pub sapling: Option<u32>,
197 pub blossom: Option<u32>,
199 pub heartwood: Option<u32>,
201 pub canopy: Option<u32>,
203 #[serde(rename = "NU5")]
205 pub nu5: Option<u32>,
206 #[serde(rename = "NU6")]
208 pub nu6: Option<u32>,
209}
210
211#[derive(Clone, Debug, Eq, PartialEq)]
213pub struct ParametersBuilder {
214 network_name: String,
216 network_magic: Magic,
218 genesis_hash: block::Hash,
220 activation_heights: BTreeMap<Height, NetworkUpgrade>,
222 slow_start_interval: Height,
224 pre_nu6_funding_streams: FundingStreams,
226 post_nu6_funding_streams: FundingStreams,
228 should_lock_funding_stream_address_period: bool,
231 target_difficulty_limit: ExpandedDifficulty,
233 disable_pow: bool,
235 should_allow_unshielded_coinbase_spends: bool,
238 pre_blossom_halving_interval: HeightDiff,
240 post_blossom_halving_interval: HeightDiff,
242}
243
244impl Default for ParametersBuilder {
245 fn default() -> Self {
247 Self {
248 network_name: "UnknownTestnet".to_string(),
249 network_magic: magics::TESTNET,
250 activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
254 genesis_hash: TESTNET_GENESIS_HASH
255 .parse()
256 .expect("hard-coded hash parses"),
257 slow_start_interval: SLOW_START_INTERVAL,
258 target_difficulty_limit: ExpandedDifficulty::from((U256::one() << 251) - 1)
268 .to_compact()
269 .to_expanded()
270 .expect("difficulty limits are valid expanded values"),
271 disable_pow: false,
272 pre_nu6_funding_streams: PRE_NU6_FUNDING_STREAMS_TESTNET.clone(),
273 post_nu6_funding_streams: POST_NU6_FUNDING_STREAMS_TESTNET.clone(),
274 should_lock_funding_stream_address_period: false,
275 pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL,
276 post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL,
277 should_allow_unshielded_coinbase_spends: false,
278 }
279 }
280}
281
282impl ParametersBuilder {
283 pub fn with_network_name(mut self, network_name: impl fmt::Display) -> Self {
285 self.network_name = network_name.to_string();
286
287 assert!(
288 !RESERVED_NETWORK_NAMES.contains(&self.network_name.as_str()),
289 "cannot use reserved network name '{network_name}' as configured Testnet name, reserved names: {RESERVED_NETWORK_NAMES:?}"
290 );
291
292 assert!(
293 self.network_name.len() <= MAX_NETWORK_NAME_LENGTH,
294 "network name {network_name} is too long, must be {MAX_NETWORK_NAME_LENGTH} characters or less"
295 );
296
297 assert!(
298 self.network_name
299 .chars()
300 .all(|x| x.is_alphanumeric() || x == '_'),
301 "network name must include only alphanumeric characters or '_'"
302 );
303
304 self
305 }
306
307 pub fn with_network_magic(mut self, network_magic: Magic) -> Self {
309 assert!(
310 [magics::MAINNET, magics::REGTEST]
311 .into_iter()
312 .all(|reserved_magic| network_magic != reserved_magic),
313 "network magic should be distinct from reserved network magics"
314 );
315
316 self.network_magic = network_magic;
317
318 self
319 }
320
321 pub fn with_genesis_hash(mut self, genesis_hash: impl fmt::Display) -> Self {
323 self.genesis_hash = genesis_hash
324 .to_string()
325 .parse()
326 .expect("configured genesis hash must parse");
327 self
328 }
329
330 pub fn with_activation_heights(
333 mut self,
334 ConfiguredActivationHeights {
335 before_overwinter,
336 overwinter,
337 sapling,
338 blossom,
339 heartwood,
340 canopy,
341 nu5,
342 nu6,
343 }: ConfiguredActivationHeights,
344 ) -> Self {
345 use NetworkUpgrade::*;
346
347 if self.should_lock_funding_stream_address_period {
348 panic!("activation heights on ParametersBuilder must not be set after setting funding streams");
349 }
350
351 let activation_heights: BTreeMap<_, _> = before_overwinter
356 .into_iter()
357 .map(|h| (h, BeforeOverwinter))
358 .chain(overwinter.into_iter().map(|h| (h, Overwinter)))
359 .chain(sapling.into_iter().map(|h| (h, Sapling)))
360 .chain(blossom.into_iter().map(|h| (h, Blossom)))
361 .chain(heartwood.into_iter().map(|h| (h, Heartwood)))
362 .chain(canopy.into_iter().map(|h| (h, Canopy)))
363 .chain(nu5.into_iter().map(|h| (h, Nu5)))
364 .chain(nu6.into_iter().map(|h| (h, Nu6)))
365 .map(|(h, nu)| (h.try_into().expect("activation height must be valid"), nu))
366 .collect();
367
368 let network_upgrades: Vec<_> = activation_heights.iter().map(|(_h, &nu)| nu).collect();
369
370 let mut activation_heights_iter = activation_heights.iter();
372 for expected_network_upgrade in NetworkUpgrade::iter() {
373 if !network_upgrades.contains(&expected_network_upgrade) {
374 continue;
375 } else if let Some((&height, &network_upgrade)) = activation_heights_iter.next() {
376 assert_ne!(
377 height,
378 Height(0),
379 "Height(0) is reserved for the `Genesis` upgrade"
380 );
381
382 assert!(
383 network_upgrade == expected_network_upgrade,
384 "network upgrades must be activated in order specified by the protocol"
385 );
386 }
387 }
388
389 self.activation_heights.split_off(&Height(1));
393 self.activation_heights.extend(activation_heights);
394
395 self
396 }
397
398 pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
400 self.slow_start_interval = slow_start_interval;
401 self
402 }
403
404 pub fn with_pre_nu6_funding_streams(
406 mut self,
407 funding_streams: ConfiguredFundingStreams,
408 ) -> Self {
409 self.pre_nu6_funding_streams =
410 funding_streams.convert_with_default(PRE_NU6_FUNDING_STREAMS_TESTNET.clone(), &self);
411 self.should_lock_funding_stream_address_period = true;
412 self
413 }
414
415 pub fn with_post_nu6_funding_streams(
417 mut self,
418 funding_streams: ConfiguredFundingStreams,
419 ) -> Self {
420 self.post_nu6_funding_streams =
421 funding_streams.convert_with_default(POST_NU6_FUNDING_STREAMS_TESTNET.clone(), &self);
422 self.should_lock_funding_stream_address_period = true;
423 self
424 }
425
426 pub fn with_target_difficulty_limit(
429 mut self,
430 target_difficulty_limit: impl Into<ExpandedDifficulty>,
431 ) -> Self {
432 self.target_difficulty_limit = target_difficulty_limit
433 .into()
434 .to_compact()
435 .to_expanded()
436 .expect("difficulty limits are valid expanded values");
437 self
438 }
439
440 pub fn with_disable_pow(mut self, disable_pow: bool) -> Self {
442 self.disable_pow = disable_pow;
443 self
444 }
445
446 pub fn with_unshielded_coinbase_spends(
448 mut self,
449 should_allow_unshielded_coinbase_spends: bool,
450 ) -> Self {
451 self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends;
452 self
453 }
454
455 pub fn with_halving_interval(mut self, pre_blossom_halving_interval: HeightDiff) -> Self {
457 if self.should_lock_funding_stream_address_period {
458 panic!("halving interval on ParametersBuilder must not be set after setting funding streams");
459 }
460
461 self.pre_blossom_halving_interval = pre_blossom_halving_interval;
462 self.post_blossom_halving_interval =
463 self.pre_blossom_halving_interval * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
464 self
465 }
466
467 fn finish(self) -> Parameters {
469 let Self {
470 network_name,
471 network_magic,
472 genesis_hash,
473 activation_heights,
474 slow_start_interval,
475 pre_nu6_funding_streams,
476 post_nu6_funding_streams,
477 should_lock_funding_stream_address_period: _,
478 target_difficulty_limit,
479 disable_pow,
480 should_allow_unshielded_coinbase_spends,
481 pre_blossom_halving_interval,
482 post_blossom_halving_interval,
483 } = self;
484 Parameters {
485 network_name,
486 network_magic,
487 genesis_hash,
488 activation_heights,
489 slow_start_interval,
490 slow_start_shift: Height(slow_start_interval.0 / 2),
491 pre_nu6_funding_streams,
492 post_nu6_funding_streams,
493 target_difficulty_limit,
494 disable_pow,
495 should_allow_unshielded_coinbase_spends,
496 pre_blossom_halving_interval,
497 post_blossom_halving_interval,
498 }
499 }
500
501 fn to_network_unchecked(&self) -> Network {
503 Network::new_configured_testnet(self.clone().finish())
504 }
505
506 pub fn to_network(self) -> Network {
508 let network = self.to_network_unchecked();
509
510 #[cfg(not(any(test, feature = "proptest-impl")))]
513 {
514 check_funding_stream_address_period(&self.pre_nu6_funding_streams, &network);
515 check_funding_stream_address_period(&self.post_nu6_funding_streams, &network);
516 }
517
518 network
519 }
520
521 pub fn is_compatible_with_default_parameters(&self) -> bool {
523 let Self {
524 network_name: _,
525 network_magic,
526 genesis_hash,
527 activation_heights,
528 slow_start_interval,
529 pre_nu6_funding_streams,
530 post_nu6_funding_streams,
531 should_lock_funding_stream_address_period: _,
532 target_difficulty_limit,
533 disable_pow,
534 should_allow_unshielded_coinbase_spends,
535 pre_blossom_halving_interval,
536 post_blossom_halving_interval,
537 } = Self::default();
538
539 self.activation_heights == activation_heights
540 && self.network_magic == network_magic
541 && self.genesis_hash == genesis_hash
542 && self.slow_start_interval == slow_start_interval
543 && self.pre_nu6_funding_streams == pre_nu6_funding_streams
544 && self.post_nu6_funding_streams == post_nu6_funding_streams
545 && self.target_difficulty_limit == target_difficulty_limit
546 && self.disable_pow == disable_pow
547 && self.should_allow_unshielded_coinbase_spends
548 == should_allow_unshielded_coinbase_spends
549 && self.pre_blossom_halving_interval == pre_blossom_halving_interval
550 && self.post_blossom_halving_interval == post_blossom_halving_interval
551 }
552}
553
554#[derive(Clone, Debug, Eq, PartialEq)]
556pub struct Parameters {
557 network_name: String,
559 network_magic: Magic,
561 genesis_hash: block::Hash,
563 activation_heights: BTreeMap<Height, NetworkUpgrade>,
569 slow_start_interval: Height,
571 slow_start_shift: Height,
573 pre_nu6_funding_streams: FundingStreams,
575 post_nu6_funding_streams: FundingStreams,
577 target_difficulty_limit: ExpandedDifficulty,
579 disable_pow: bool,
581 should_allow_unshielded_coinbase_spends: bool,
584 pre_blossom_halving_interval: HeightDiff,
586 post_blossom_halving_interval: HeightDiff,
588}
589
590impl Default for Parameters {
591 fn default() -> Self {
593 Self {
594 network_name: "Testnet".to_string(),
595 ..Self::build().finish()
596 }
597 }
598}
599
600impl Parameters {
601 pub fn build() -> ParametersBuilder {
603 ParametersBuilder::default()
604 }
605
606 pub fn new_regtest(
610 nu5_activation_height: Option<u32>,
611 nu6_activation_height: Option<u32>,
612 ) -> Self {
613 #[cfg(any(test, feature = "proptest-impl"))]
614 let nu5_activation_height = nu5_activation_height.or(Some(100));
615
616 let parameters = Self::build()
617 .with_genesis_hash(REGTEST_GENESIS_HASH)
618 .with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))
620 .with_disable_pow(true)
621 .with_unshielded_coinbase_spends(true)
622 .with_slow_start_interval(Height::MIN)
623 .with_activation_heights(ConfiguredActivationHeights {
626 canopy: Some(1),
627 nu5: nu5_activation_height,
628 nu6: nu6_activation_height,
629 ..Default::default()
630 })
631 .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL);
632
633 let parameters = parameters
636 .with_pre_nu6_funding_streams(ConfiguredFundingStreams::empty())
637 .with_post_nu6_funding_streams(ConfiguredFundingStreams::empty());
638
639 Self {
640 network_name: "Regtest".to_string(),
641 network_magic: magics::REGTEST,
642 ..parameters.finish()
643 }
644 }
645
646 pub fn is_default_testnet(&self) -> bool {
648 self == &Self::default()
649 }
650
651 pub fn is_regtest(&self) -> bool {
653 if self.network_magic != magics::REGTEST {
654 return false;
655 }
656
657 let Self {
658 network_name,
659 network_magic: _,
661 genesis_hash,
662 activation_heights: _,
664 slow_start_interval,
665 slow_start_shift,
666 pre_nu6_funding_streams,
667 post_nu6_funding_streams,
668 target_difficulty_limit,
669 disable_pow,
670 should_allow_unshielded_coinbase_spends,
671 pre_blossom_halving_interval,
672 post_blossom_halving_interval,
673 } = Self::new_regtest(None, None);
674
675 self.network_name == network_name
676 && self.genesis_hash == genesis_hash
677 && self.slow_start_interval == slow_start_interval
678 && self.slow_start_shift == slow_start_shift
679 && self.pre_nu6_funding_streams == pre_nu6_funding_streams
680 && self.post_nu6_funding_streams == post_nu6_funding_streams
681 && self.target_difficulty_limit == target_difficulty_limit
682 && self.disable_pow == disable_pow
683 && self.should_allow_unshielded_coinbase_spends
684 == should_allow_unshielded_coinbase_spends
685 && self.pre_blossom_halving_interval == pre_blossom_halving_interval
686 && self.post_blossom_halving_interval == post_blossom_halving_interval
687 }
688
689 pub fn network_name(&self) -> &str {
691 &self.network_name
692 }
693
694 pub fn network_magic(&self) -> Magic {
696 self.network_magic
697 }
698
699 pub fn genesis_hash(&self) -> block::Hash {
701 self.genesis_hash
702 }
703
704 pub fn activation_heights(&self) -> &BTreeMap<Height, NetworkUpgrade> {
706 &self.activation_heights
707 }
708
709 pub fn slow_start_interval(&self) -> Height {
711 self.slow_start_interval
712 }
713
714 pub fn slow_start_shift(&self) -> Height {
716 self.slow_start_shift
717 }
718
719 pub fn pre_nu6_funding_streams(&self) -> &FundingStreams {
721 &self.pre_nu6_funding_streams
722 }
723
724 pub fn post_nu6_funding_streams(&self) -> &FundingStreams {
726 &self.post_nu6_funding_streams
727 }
728
729 pub fn target_difficulty_limit(&self) -> ExpandedDifficulty {
731 self.target_difficulty_limit
732 }
733
734 pub fn disable_pow(&self) -> bool {
736 self.disable_pow
737 }
738
739 pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
742 self.should_allow_unshielded_coinbase_spends
743 }
744
745 pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
747 self.pre_blossom_halving_interval
748 }
749
750 pub fn post_blossom_halving_interval(&self) -> HeightDiff {
752 self.post_blossom_halving_interval
753 }
754}
755
756impl Network {
757 pub fn disable_pow(&self) -> bool {
759 if let Self::Testnet(params) = self {
760 params.disable_pow()
761 } else {
762 false
763 }
764 }
765
766 pub fn slow_start_interval(&self) -> Height {
768 if let Self::Testnet(params) = self {
769 params.slow_start_interval()
770 } else {
771 SLOW_START_INTERVAL
772 }
773 }
774
775 pub fn slow_start_shift(&self) -> Height {
777 if let Self::Testnet(params) = self {
778 params.slow_start_shift()
779 } else {
780 SLOW_START_SHIFT
781 }
782 }
783
784 pub fn pre_nu6_funding_streams(&self) -> &FundingStreams {
792 if let Self::Testnet(params) = self {
793 params.pre_nu6_funding_streams()
794 } else {
795 &PRE_NU6_FUNDING_STREAMS_MAINNET
796 }
797 }
798
799 pub fn post_nu6_funding_streams(&self) -> &FundingStreams {
805 if let Self::Testnet(params) = self {
806 params.post_nu6_funding_streams()
807 } else {
808 &POST_NU6_FUNDING_STREAMS_MAINNET
809 }
810 }
811
812 pub fn funding_streams(&self, height: Height) -> &FundingStreams {
814 if NetworkUpgrade::current(self, height) < NetworkUpgrade::Nu6 {
815 self.pre_nu6_funding_streams()
816 } else {
817 self.post_nu6_funding_streams()
818 }
819 }
820
821 pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
824 if let Self::Testnet(params) = self {
825 params.should_allow_unshielded_coinbase_spends()
826 } else {
827 false
828 }
829 }
830}