1use NetworkUpgrade::*;
4
5use crate::block;
6use crate::parameters::{Network, Network::*};
7
8use std::collections::{BTreeMap, HashMap};
9use std::fmt;
10
11use chrono::{DateTime, Duration, Utc};
12use hex::{FromHex, ToHex};
13
14#[cfg(any(test, feature = "proptest-impl"))]
15use proptest_derive::Arbitrary;
16
17const NETWORK_UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[
19 Genesis,
20 BeforeOverwinter,
21 Overwinter,
22 Sapling,
23 Blossom,
24 Heartwood,
25 Canopy,
26 Nu5,
27 Nu6,
28 Nu6_1,
29 #[cfg(any(test, feature = "zebra-test"))]
30 Nu7,
31];
32
33#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
38#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
39pub enum NetworkUpgrade {
40 Genesis,
46 BeforeOverwinter,
51 Overwinter,
53 Sapling,
55 Blossom,
57 Heartwood,
59 Canopy,
61 #[serde(rename = "NU5")]
63 Nu5,
64 #[serde(rename = "NU6")]
66 Nu6,
67 #[serde(rename = "NU6.1")]
69 Nu6_1,
70 #[serde(rename = "NU7")]
72 Nu7,
73}
74
75impl TryFrom<u32> for NetworkUpgrade {
76 type Error = crate::Error;
77
78 fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
79 CONSENSUS_BRANCH_IDS
80 .iter()
81 .find(|id| id.1 == ConsensusBranchId(branch_id))
82 .map(|nu| nu.0)
83 .ok_or(Self::Error::InvalidConsensusBranchId)
84 }
85}
86
87impl fmt::Display for NetworkUpgrade {
88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 fmt::Debug::fmt(self, f)
91 }
92}
93
94#[allow(unused)]
104pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
105 (block::Height(0), Genesis),
106 (block::Height(1), BeforeOverwinter),
107 (block::Height(347_500), Overwinter),
108 (block::Height(419_200), Sapling),
109 (block::Height(653_600), Blossom),
110 (block::Height(903_000), Heartwood),
111 (block::Height(1_046_400), Canopy),
112 (block::Height(1_687_104), Nu5),
113 (block::Height(2_726_400), Nu6),
114];
115
116#[allow(unused)]
118const FAKE_MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
119 (block::Height(0), Genesis),
120 (block::Height(5), BeforeOverwinter),
121 (block::Height(10), Overwinter),
122 (block::Height(15), Sapling),
123 (block::Height(20), Blossom),
124 (block::Height(25), Heartwood),
125 (block::Height(30), Canopy),
126 (block::Height(35), Nu5),
127 (block::Height(40), Nu6),
128 (block::Height(45), Nu6_1),
129 (block::Height(50), Nu7),
130];
131
132pub const NU6_1_ACTIVATION_HEIGHT_TESTNET: block::Height = block::Height(3_536_500);
136
137#[allow(unused)]
147pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
148 (block::Height(0), Genesis),
149 (block::Height(1), BeforeOverwinter),
150 (block::Height(207_500), Overwinter),
151 (block::Height(280_000), Sapling),
152 (block::Height(584_000), Blossom),
153 (block::Height(903_800), Heartwood),
154 (block::Height(1_028_500), Canopy),
155 (block::Height(1_842_420), Nu5),
156 (block::Height(2_976_000), Nu6),
157 (NU6_1_ACTIVATION_HEIGHT_TESTNET, Nu6_1),
158];
159
160#[allow(unused)]
162const FAKE_TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
163 (block::Height(0), Genesis),
164 (block::Height(5), BeforeOverwinter),
165 (block::Height(10), Overwinter),
166 (block::Height(15), Sapling),
167 (block::Height(20), Blossom),
168 (block::Height(25), Heartwood),
169 (block::Height(30), Canopy),
170 (block::Height(35), Nu5),
171 (block::Height(40), Nu6),
172 (block::Height(45), Nu6_1),
173 (block::Height(50), Nu7),
174];
175
176#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
179pub struct ConsensusBranchId(pub(crate) u32);
180
181impl ConsensusBranchId {
182 fn bytes_in_display_order(&self) -> [u8; 4] {
187 self.0.to_be_bytes()
188 }
189}
190
191impl From<ConsensusBranchId> for u32 {
192 fn from(branch: ConsensusBranchId) -> u32 {
193 branch.0
194 }
195}
196
197impl From<u32> for ConsensusBranchId {
198 fn from(branch: u32) -> Self {
199 ConsensusBranchId(branch)
200 }
201}
202
203impl ToHex for &ConsensusBranchId {
204 fn encode_hex<T: FromIterator<char>>(&self) -> T {
205 self.bytes_in_display_order().encode_hex()
206 }
207
208 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
209 self.bytes_in_display_order().encode_hex_upper()
210 }
211}
212
213impl ToHex for ConsensusBranchId {
214 fn encode_hex<T: FromIterator<char>>(&self) -> T {
215 self.bytes_in_display_order().encode_hex()
216 }
217
218 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
219 self.bytes_in_display_order().encode_hex_upper()
220 }
221}
222
223impl FromHex for ConsensusBranchId {
224 type Error = <[u8; 4] as FromHex>::Error;
225
226 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
227 let branch = <[u8; 4]>::from_hex(hex)?;
228 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
229 }
230}
231
232impl fmt::Display for ConsensusBranchId {
233 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234 f.write_str(&self.encode_hex::<String>())
235 }
236}
237
238impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
239 type Error = crate::Error;
240
241 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
242 zcash_primitives::consensus::BranchId::try_from(u32::from(id))
243 .map_err(|_| Self::Error::InvalidConsensusBranchId)
244 }
245}
246
247pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
258 (Overwinter, ConsensusBranchId(0x5ba81b19)),
259 (Sapling, ConsensusBranchId(0x76b809bb)),
260 (Blossom, ConsensusBranchId(0x2bb40e60)),
261 (Heartwood, ConsensusBranchId(0xf5b9230b)),
262 (Canopy, ConsensusBranchId(0xe9ff75a6)),
263 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
264 (Nu6, ConsensusBranchId(0xc8e71055)),
265 (Nu6_1, ConsensusBranchId(0x4dec4df0)),
266 #[cfg(any(test, feature = "zebra-test"))]
267 (Nu7, ConsensusBranchId(0x77190ad8)),
268];
269
270const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
272
273pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
275
276pub const POW_AVERAGING_WINDOW: usize = 17;
280
281const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
286
287const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
291
292pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
297
298impl Network {
299 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
314 match self {
315 #[cfg(feature = "zebra-test")]
327 Mainnet if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => {
328 FAKE_MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect()
329 }
330 #[cfg(feature = "zebra-test")]
331 Testnet(_) if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => {
332 FAKE_TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect()
333 }
334 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
335 Testnet(params) => params.activation_heights().clone(),
336 }
337 }
338
339 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
342 NETWORK_UPGRADES_IN_ORDER
343 .iter()
344 .map_while(|&nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
345 .collect()
346 }
347}
348
349impl NetworkUpgrade {
350 pub fn current_with_activation_height(
352 network: &Network,
353 height: block::Height,
354 ) -> (NetworkUpgrade, block::Height) {
355 network
356 .activation_list()
357 .range(..=height)
358 .map(|(&h, &nu)| (nu, h))
359 .next_back()
360 .expect("every height has a current network upgrade")
361 }
362
363 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
365 network
366 .activation_list()
367 .range(..=height)
368 .map(|(_, nu)| *nu)
369 .next_back()
370 .expect("every height has a current network upgrade")
371 }
372
373 pub fn next_upgrade(self) -> Option<Self> {
375 Self::iter().skip_while(|&nu| self != nu).nth(1)
376 }
377
378 pub fn previous_upgrade(self) -> Option<Self> {
380 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
381 }
382
383 #[cfg(test)]
388 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
389 use std::ops::Bound::*;
390
391 network
392 .activation_list()
393 .range((Excluded(height), Unbounded))
394 .map(|(_, nu)| *nu)
395 .next()
396 }
397
398 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
410 network
411 .activation_list()
412 .iter()
413 .find(|(_, nu)| nu == &self)
414 .map(|(height, _)| *height)
415 .or_else(|| {
416 self.next_upgrade()
417 .and_then(|next_nu| next_nu.activation_height(network))
418 })
419 }
420
421 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
427 network.activation_list().contains_key(&height)
428 }
429
430 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
439 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
440 }
441
442 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
446 NetworkUpgrade::branch_id_list().get(self).cloned()
447 }
448
449 pub fn target_spacing(&self) -> Duration {
454 let spacing_seconds = match self {
455 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
456 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu7 => {
457 POST_BLOSSOM_POW_TARGET_SPACING.into()
458 }
459 };
460
461 Duration::seconds(spacing_seconds)
462 }
463
464 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
468 NetworkUpgrade::current(network, height).target_spacing()
469 }
470
471 pub fn target_spacings(
473 network: &Network,
474 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
475 [
476 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
477 (
478 NetworkUpgrade::Blossom,
479 POST_BLOSSOM_POW_TARGET_SPACING.into(),
480 ),
481 ]
482 .into_iter()
483 .filter_map(move |(upgrade, spacing_seconds)| {
484 let activation_height = upgrade.activation_height(network)?;
485 let target_spacing = Duration::seconds(spacing_seconds);
486 Some((activation_height, target_spacing))
487 })
488 }
489
490 pub fn minimum_difficulty_spacing_for_height(
495 network: &Network,
496 height: block::Height,
497 ) -> Option<Duration> {
498 match (network, height) {
499 (Network::Testnet(_params), height)
501 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
502 {
503 None
504 }
505 (Network::Mainnet, _) => None,
506 (Network::Testnet(_params), _) => {
507 let network_upgrade = NetworkUpgrade::current(network, height);
508 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
509 }
510 }
511 }
512
513 pub fn is_testnet_min_difficulty_block(
529 network: &Network,
530 block_height: block::Height,
531 block_time: DateTime<Utc>,
532 previous_block_time: DateTime<Utc>,
533 ) -> bool {
534 let block_time_gap = block_time - previous_block_time;
535 if let Some(min_difficulty_gap) =
536 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
537 {
538 block_time_gap > min_difficulty_gap
539 } else {
540 false
541 }
542 }
543
544 pub fn averaging_window_timespan(&self) -> Duration {
548 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
549 }
550
551 pub fn averaging_window_timespan_for_height(
555 network: &Network,
556 height: block::Height,
557 ) -> Duration {
558 NetworkUpgrade::current(network, height).averaging_window_timespan()
559 }
560
561 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
563 NETWORK_UPGRADES_IN_ORDER.iter().copied()
564 }
565}
566
567impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
568 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
569 match nu {
570 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
571 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
572 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
573 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
574 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
575 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
576 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
577 zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
578 }
580 }
581}
582
583impl ConsensusBranchId {
584 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
593
594 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
598 NetworkUpgrade::current(network, height).branch_id()
599 }
600}