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; 9] = [
19 Genesis,
20 BeforeOverwinter,
21 Overwinter,
22 Sapling,
23 Blossom,
24 Heartwood,
25 Canopy,
26 Nu5,
27 Nu6,
28];
29
30#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
35#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
36pub enum NetworkUpgrade {
37 Genesis,
43 BeforeOverwinter,
48 Overwinter,
50 Sapling,
52 Blossom,
54 Heartwood,
56 Canopy,
58 #[serde(rename = "NU5")]
60 Nu5,
61 #[serde(rename = "NU6")]
63 Nu6,
64}
65
66impl TryFrom<u32> for NetworkUpgrade {
67 type Error = crate::Error;
68
69 fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
70 CONSENSUS_BRANCH_IDS
71 .iter()
72 .find(|id| id.1 == ConsensusBranchId(branch_id))
73 .map(|nu| nu.0)
74 .ok_or(Self::Error::InvalidConsensusBranchId)
75 }
76}
77
78impl fmt::Display for NetworkUpgrade {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 fmt::Debug::fmt(self, f)
82 }
83}
84
85#[allow(unused)]
95pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
96 (block::Height(0), Genesis),
97 (block::Height(1), BeforeOverwinter),
98 (block::Height(347_500), Overwinter),
99 (block::Height(419_200), Sapling),
100 (block::Height(653_600), Blossom),
101 (block::Height(903_000), Heartwood),
102 (block::Height(1_046_400), Canopy),
103 (block::Height(1_687_104), Nu5),
104 (block::Height(2_726_400), Nu6),
105];
106
107#[allow(unused)]
109const FAKE_MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
110 (block::Height(0), Genesis),
111 (block::Height(5), BeforeOverwinter),
112 (block::Height(10), Overwinter),
113 (block::Height(15), Sapling),
114 (block::Height(20), Blossom),
115 (block::Height(25), Heartwood),
116 (block::Height(30), Canopy),
117 (block::Height(35), Nu5),
118 (block::Height(40), Nu6),
119];
120
121#[allow(unused)]
131pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
132 (block::Height(0), Genesis),
133 (block::Height(1), BeforeOverwinter),
134 (block::Height(207_500), Overwinter),
135 (block::Height(280_000), Sapling),
136 (block::Height(584_000), Blossom),
137 (block::Height(903_800), Heartwood),
138 (block::Height(1_028_500), Canopy),
139 (block::Height(1_842_420), Nu5),
140 (block::Height(2_976_000), Nu6),
141];
142
143#[allow(unused)]
145const FAKE_TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
146 (block::Height(0), Genesis),
147 (block::Height(5), BeforeOverwinter),
148 (block::Height(10), Overwinter),
149 (block::Height(15), Sapling),
150 (block::Height(20), Blossom),
151 (block::Height(25), Heartwood),
152 (block::Height(30), Canopy),
153 (block::Height(35), Nu5),
154 (block::Height(40), Nu6),
155];
156
157#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
160pub struct ConsensusBranchId(pub(crate) u32);
161
162impl ConsensusBranchId {
163 fn bytes_in_display_order(&self) -> [u8; 4] {
168 self.0.to_be_bytes()
169 }
170}
171
172impl From<ConsensusBranchId> for u32 {
173 fn from(branch: ConsensusBranchId) -> u32 {
174 branch.0
175 }
176}
177
178impl From<u32> for ConsensusBranchId {
179 fn from(branch: u32) -> Self {
180 ConsensusBranchId(branch)
181 }
182}
183
184impl ToHex for &ConsensusBranchId {
185 fn encode_hex<T: FromIterator<char>>(&self) -> T {
186 self.bytes_in_display_order().encode_hex()
187 }
188
189 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
190 self.bytes_in_display_order().encode_hex_upper()
191 }
192}
193
194impl ToHex for ConsensusBranchId {
195 fn encode_hex<T: FromIterator<char>>(&self) -> T {
196 self.bytes_in_display_order().encode_hex()
197 }
198
199 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
200 self.bytes_in_display_order().encode_hex_upper()
201 }
202}
203
204impl FromHex for ConsensusBranchId {
205 type Error = <[u8; 4] as FromHex>::Error;
206
207 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
208 let branch = <[u8; 4]>::from_hex(hex)?;
209 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
210 }
211}
212
213impl fmt::Display for ConsensusBranchId {
214 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215 f.write_str(&self.encode_hex::<String>())
216 }
217}
218
219impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
220 type Error = crate::Error;
221
222 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
223 zcash_primitives::consensus::BranchId::try_from(u32::from(id))
224 .map_err(|_| Self::Error::InvalidConsensusBranchId)
225 }
226}
227
228pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
239 (Overwinter, ConsensusBranchId(0x5ba81b19)),
240 (Sapling, ConsensusBranchId(0x76b809bb)),
241 (Blossom, ConsensusBranchId(0x2bb40e60)),
242 (Heartwood, ConsensusBranchId(0xf5b9230b)),
243 (Canopy, ConsensusBranchId(0xe9ff75a6)),
244 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
245 (Nu6, ConsensusBranchId(0xc8e71055)),
246];
247
248const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
250
251pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
253
254pub const POW_AVERAGING_WINDOW: usize = 17;
258
259const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
264
265const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
269
270pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
275
276impl Network {
277 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
292 match self {
293 #[cfg(feature = "zebra-test")]
305 Mainnet if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => {
306 FAKE_MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect()
307 }
308 #[cfg(feature = "zebra-test")]
309 Testnet(_) if std::env::var_os("TEST_FAKE_ACTIVATION_HEIGHTS").is_some() => {
310 FAKE_TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect()
311 }
312 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
313 Testnet(params) => params.activation_heights().clone(),
314 }
315 }
316
317 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
320 NETWORK_UPGRADES_IN_ORDER
321 .into_iter()
322 .map_while(|nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
323 .collect()
324 }
325}
326
327impl NetworkUpgrade {
328 pub fn current_with_activation_height(
330 network: &Network,
331 height: block::Height,
332 ) -> (NetworkUpgrade, block::Height) {
333 network
334 .activation_list()
335 .range(..=height)
336 .map(|(&h, &nu)| (nu, h))
337 .next_back()
338 .expect("every height has a current network upgrade")
339 }
340
341 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
343 network
344 .activation_list()
345 .range(..=height)
346 .map(|(_, nu)| *nu)
347 .next_back()
348 .expect("every height has a current network upgrade")
349 }
350
351 pub fn next_upgrade(self) -> Option<Self> {
353 Self::iter().skip_while(|&nu| self != nu).nth(1)
354 }
355
356 pub fn previous_upgrade(self) -> Option<Self> {
358 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
359 }
360
361 #[cfg(test)]
366 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
367 use std::ops::Bound::*;
368
369 network
370 .activation_list()
371 .range((Excluded(height), Unbounded))
372 .map(|(_, nu)| *nu)
373 .next()
374 }
375
376 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
388 network
389 .activation_list()
390 .iter()
391 .find(|(_, nu)| nu == &self)
392 .map(|(height, _)| *height)
393 .or_else(|| {
394 self.next_upgrade()
395 .and_then(|next_nu| next_nu.activation_height(network))
396 })
397 }
398
399 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
405 network.activation_list().contains_key(&height)
406 }
407
408 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
417 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
418 }
419
420 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
424 NetworkUpgrade::branch_id_list().get(self).cloned()
425 }
426
427 pub fn target_spacing(&self) -> Duration {
432 let spacing_seconds = match self {
433 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
434 Blossom | Heartwood | Canopy | Nu5 | Nu6 => POST_BLOSSOM_POW_TARGET_SPACING.into(),
435 };
436
437 Duration::seconds(spacing_seconds)
438 }
439
440 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
444 NetworkUpgrade::current(network, height).target_spacing()
445 }
446
447 pub fn target_spacings(
449 network: &Network,
450 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
451 [
452 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
453 (
454 NetworkUpgrade::Blossom,
455 POST_BLOSSOM_POW_TARGET_SPACING.into(),
456 ),
457 ]
458 .into_iter()
459 .filter_map(move |(upgrade, spacing_seconds)| {
460 let activation_height = upgrade.activation_height(network)?;
461 let target_spacing = Duration::seconds(spacing_seconds);
462 Some((activation_height, target_spacing))
463 })
464 }
465
466 pub fn minimum_difficulty_spacing_for_height(
471 network: &Network,
472 height: block::Height,
473 ) -> Option<Duration> {
474 match (network, height) {
475 (Network::Testnet(_params), height)
477 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
478 {
479 None
480 }
481 (Network::Mainnet, _) => None,
482 (Network::Testnet(_params), _) => {
483 let network_upgrade = NetworkUpgrade::current(network, height);
484 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
485 }
486 }
487 }
488
489 pub fn is_testnet_min_difficulty_block(
505 network: &Network,
506 block_height: block::Height,
507 block_time: DateTime<Utc>,
508 previous_block_time: DateTime<Utc>,
509 ) -> bool {
510 let block_time_gap = block_time - previous_block_time;
511 if let Some(min_difficulty_gap) =
512 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
513 {
514 block_time_gap > min_difficulty_gap
515 } else {
516 false
517 }
518 }
519
520 pub fn averaging_window_timespan(&self) -> Duration {
524 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
525 }
526
527 pub fn averaging_window_timespan_for_height(
531 network: &Network,
532 height: block::Height,
533 ) -> Duration {
534 NetworkUpgrade::current(network, height).averaging_window_timespan()
535 }
536
537 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
539 NETWORK_UPGRADES_IN_ORDER.into_iter()
540 }
541}
542
543impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
544 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
545 match nu {
546 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
547 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
548 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
549 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
550 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
551 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
552 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
553 }
554 }
555}
556
557impl ConsensusBranchId {
558 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
567
568 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
572 NetworkUpgrade::current(network, height).branch_id()
573 }
574}