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 #[cfg(any(test, feature = "zebra-test"))]
29 Nu6_1,
30 #[cfg(any(test, feature = "zebra-test"))]
31 Nu7,
32];
33
34#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
39#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
40pub enum NetworkUpgrade {
41 Genesis,
47 BeforeOverwinter,
52 Overwinter,
54 Sapling,
56 Blossom,
58 Heartwood,
60 Canopy,
62 #[serde(rename = "NU5")]
64 Nu5,
65 #[serde(rename = "NU6")]
67 Nu6,
68 #[serde(rename = "NU6.1")]
70 Nu6_1,
71 #[serde(rename = "NU7")]
73 Nu7,
74}
75
76impl TryFrom<u32> for NetworkUpgrade {
77 type Error = crate::Error;
78
79 fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
80 CONSENSUS_BRANCH_IDS
81 .iter()
82 .find(|id| id.1 == ConsensusBranchId(branch_id))
83 .map(|nu| nu.0)
84 .ok_or(Self::Error::InvalidConsensusBranchId)
85 }
86}
87
88impl fmt::Display for NetworkUpgrade {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 fmt::Debug::fmt(self, f)
92 }
93}
94
95#[allow(unused)]
105pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
106 (block::Height(0), Genesis),
107 (block::Height(1), BeforeOverwinter),
108 (block::Height(347_500), Overwinter),
109 (block::Height(419_200), Sapling),
110 (block::Height(653_600), Blossom),
111 (block::Height(903_000), Heartwood),
112 (block::Height(1_046_400), Canopy),
113 (block::Height(1_687_104), Nu5),
114 (block::Height(2_726_400), Nu6),
115];
116
117#[allow(unused)]
119const FAKE_MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
120 (block::Height(0), Genesis),
121 (block::Height(5), BeforeOverwinter),
122 (block::Height(10), Overwinter),
123 (block::Height(15), Sapling),
124 (block::Height(20), Blossom),
125 (block::Height(25), Heartwood),
126 (block::Height(30), Canopy),
127 (block::Height(35), Nu5),
128 (block::Height(40), Nu6),
129 (block::Height(45), Nu6_1),
130 (block::Height(50), Nu7),
131];
132
133#[allow(unused)]
143pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
144 (block::Height(0), Genesis),
145 (block::Height(1), BeforeOverwinter),
146 (block::Height(207_500), Overwinter),
147 (block::Height(280_000), Sapling),
148 (block::Height(584_000), Blossom),
149 (block::Height(903_800), Heartwood),
150 (block::Height(1_028_500), Canopy),
151 (block::Height(1_842_420), Nu5),
152 (block::Height(2_976_000), Nu6),
153];
154
155#[allow(unused)]
157const FAKE_TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
158 (block::Height(0), Genesis),
159 (block::Height(5), BeforeOverwinter),
160 (block::Height(10), Overwinter),
161 (block::Height(15), Sapling),
162 (block::Height(20), Blossom),
163 (block::Height(25), Heartwood),
164 (block::Height(30), Canopy),
165 (block::Height(35), Nu5),
166 (block::Height(40), Nu6),
167 (block::Height(45), Nu6_1),
168 (block::Height(50), Nu7),
169];
170
171#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
174pub struct ConsensusBranchId(pub(crate) u32);
175
176impl ConsensusBranchId {
177 fn bytes_in_display_order(&self) -> [u8; 4] {
182 self.0.to_be_bytes()
183 }
184}
185
186impl From<ConsensusBranchId> for u32 {
187 fn from(branch: ConsensusBranchId) -> u32 {
188 branch.0
189 }
190}
191
192impl From<u32> for ConsensusBranchId {
193 fn from(branch: u32) -> Self {
194 ConsensusBranchId(branch)
195 }
196}
197
198impl ToHex for &ConsensusBranchId {
199 fn encode_hex<T: FromIterator<char>>(&self) -> T {
200 self.bytes_in_display_order().encode_hex()
201 }
202
203 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
204 self.bytes_in_display_order().encode_hex_upper()
205 }
206}
207
208impl ToHex for ConsensusBranchId {
209 fn encode_hex<T: FromIterator<char>>(&self) -> T {
210 self.bytes_in_display_order().encode_hex()
211 }
212
213 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
214 self.bytes_in_display_order().encode_hex_upper()
215 }
216}
217
218impl FromHex for ConsensusBranchId {
219 type Error = <[u8; 4] as FromHex>::Error;
220
221 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
222 let branch = <[u8; 4]>::from_hex(hex)?;
223 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
224 }
225}
226
227impl fmt::Display for ConsensusBranchId {
228 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229 f.write_str(&self.encode_hex::<String>())
230 }
231}
232
233impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
234 type Error = crate::Error;
235
236 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
237 zcash_primitives::consensus::BranchId::try_from(u32::from(id))
238 .map_err(|_| Self::Error::InvalidConsensusBranchId)
239 }
240}
241
242pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
253 (Overwinter, ConsensusBranchId(0x5ba81b19)),
254 (Sapling, ConsensusBranchId(0x76b809bb)),
255 (Blossom, ConsensusBranchId(0x2bb40e60)),
256 (Heartwood, ConsensusBranchId(0xf5b9230b)),
257 (Canopy, ConsensusBranchId(0xe9ff75a6)),
258 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
259 (Nu6, ConsensusBranchId(0xc8e71055)),
260 #[cfg(any(test, feature = "zebra-test"))]
261 (Nu6_1, ConsensusBranchId(0x4dec4df0)),
262 #[cfg(any(test, feature = "zebra-test"))]
263 (Nu7, ConsensusBranchId(0x77190ad8)),
264];
265
266const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
268
269pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
271
272pub const POW_AVERAGING_WINDOW: usize = 17;
276
277const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
282
283const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
287
288pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
293
294impl Network {
295 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
310 match self {
311 #[cfg(feature = "zebra-test")]
323 Mainnet if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => {
324 FAKE_MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect()
325 }
326 #[cfg(feature = "zebra-test")]
327 Testnet(_) if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => {
328 FAKE_TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect()
329 }
330 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
331 Testnet(params) => params.activation_heights().clone(),
332 }
333 }
334
335 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
338 NETWORK_UPGRADES_IN_ORDER
339 .iter()
340 .map_while(|&nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
341 .collect()
342 }
343}
344
345impl NetworkUpgrade {
346 pub fn current_with_activation_height(
348 network: &Network,
349 height: block::Height,
350 ) -> (NetworkUpgrade, block::Height) {
351 network
352 .activation_list()
353 .range(..=height)
354 .map(|(&h, &nu)| (nu, h))
355 .next_back()
356 .expect("every height has a current network upgrade")
357 }
358
359 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
361 network
362 .activation_list()
363 .range(..=height)
364 .map(|(_, nu)| *nu)
365 .next_back()
366 .expect("every height has a current network upgrade")
367 }
368
369 pub fn next_upgrade(self) -> Option<Self> {
371 Self::iter().skip_while(|&nu| self != nu).nth(1)
372 }
373
374 pub fn previous_upgrade(self) -> Option<Self> {
376 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
377 }
378
379 #[cfg(test)]
384 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
385 use std::ops::Bound::*;
386
387 network
388 .activation_list()
389 .range((Excluded(height), Unbounded))
390 .map(|(_, nu)| *nu)
391 .next()
392 }
393
394 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
406 network
407 .activation_list()
408 .iter()
409 .find(|(_, nu)| nu == &self)
410 .map(|(height, _)| *height)
411 .or_else(|| {
412 self.next_upgrade()
413 .and_then(|next_nu| next_nu.activation_height(network))
414 })
415 }
416
417 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
423 network.activation_list().contains_key(&height)
424 }
425
426 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
435 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
436 }
437
438 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
442 NetworkUpgrade::branch_id_list().get(self).cloned()
443 }
444
445 pub fn target_spacing(&self) -> Duration {
450 let spacing_seconds = match self {
451 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
452 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu7 => {
453 POST_BLOSSOM_POW_TARGET_SPACING.into()
454 }
455 };
456
457 Duration::seconds(spacing_seconds)
458 }
459
460 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
464 NetworkUpgrade::current(network, height).target_spacing()
465 }
466
467 pub fn target_spacings(
469 network: &Network,
470 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
471 [
472 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
473 (
474 NetworkUpgrade::Blossom,
475 POST_BLOSSOM_POW_TARGET_SPACING.into(),
476 ),
477 ]
478 .into_iter()
479 .filter_map(move |(upgrade, spacing_seconds)| {
480 let activation_height = upgrade.activation_height(network)?;
481 let target_spacing = Duration::seconds(spacing_seconds);
482 Some((activation_height, target_spacing))
483 })
484 }
485
486 pub fn minimum_difficulty_spacing_for_height(
491 network: &Network,
492 height: block::Height,
493 ) -> Option<Duration> {
494 match (network, height) {
495 (Network::Testnet(_params), height)
497 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
498 {
499 None
500 }
501 (Network::Mainnet, _) => None,
502 (Network::Testnet(_params), _) => {
503 let network_upgrade = NetworkUpgrade::current(network, height);
504 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
505 }
506 }
507 }
508
509 pub fn is_testnet_min_difficulty_block(
525 network: &Network,
526 block_height: block::Height,
527 block_time: DateTime<Utc>,
528 previous_block_time: DateTime<Utc>,
529 ) -> bool {
530 let block_time_gap = block_time - previous_block_time;
531 if let Some(min_difficulty_gap) =
532 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
533 {
534 block_time_gap > min_difficulty_gap
535 } else {
536 false
537 }
538 }
539
540 pub fn averaging_window_timespan(&self) -> Duration {
544 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
545 }
546
547 pub fn averaging_window_timespan_for_height(
551 network: &Network,
552 height: block::Height,
553 ) -> Duration {
554 NetworkUpgrade::current(network, height).averaging_window_timespan()
555 }
556
557 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
559 NETWORK_UPGRADES_IN_ORDER.iter().copied()
560 }
561}
562
563impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
564 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
565 match nu {
566 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
567 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
568 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
569 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
570 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
571 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
572 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
573 }
575 }
576}
577
578impl ConsensusBranchId {
579 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
588
589 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
593 NetworkUpgrade::current(network, height).branch_id()
594 }
595}