1use NetworkUpgrade::*;
4
5use crate::block;
6use crate::parameters::{Network, Network::*};
7use crate::serialization::BytesInDisplayOrder;
8
9use std::collections::{BTreeMap, HashMap};
10use std::fmt;
11
12use chrono::{DateTime, Duration, Utc};
13use hex::{FromHex, ToHex};
14
15use strum::{EnumIter, IntoEnumIterator};
16
17#[cfg(any(test, feature = "proptest-impl"))]
18use proptest_derive::Arbitrary;
19
20#[derive(
27 Copy, Clone, EnumIter, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd,
28)]
29#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
30pub enum NetworkUpgrade {
31 Genesis,
37 BeforeOverwinter,
42 Overwinter,
44 Sapling,
46 Blossom,
48 Heartwood,
50 Canopy,
52 #[serde(rename = "NU5")]
54 Nu5,
55 #[serde(rename = "NU6")]
57 Nu6,
58 #[serde(rename = "NU6.1")]
60 Nu6_1,
61 #[serde(rename = "NU6.2")]
63 Nu6_2,
64 #[serde(rename = "NU6.3")]
66 Nu6_3,
67 #[serde(rename = "NU7")]
69 Nu7,
70
71 #[cfg(zcash_unstable = "zfuture")]
72 ZFuture,
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 use super::constants::activation_heights::mainnet::*;
106 &[
107 (block::Height(0), Genesis),
108 (BEFORE_OVERWINTER, BeforeOverwinter),
109 (OVERWINTER, Overwinter),
110 (SAPLING, Sapling),
111 (BLOSSOM, Blossom),
112 (HEARTWOOD, Heartwood),
113 (CANOPY, Canopy),
114 (NU5, Nu5),
115 (NU6, Nu6),
116 (NU6_1, Nu6_1),
117 (NU6_2, Nu6_2),
118 ]
119};
120#[allow(unused)]
130pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
131 use super::constants::activation_heights::testnet::*;
132 &[
133 (block::Height(0), Genesis),
134 (BEFORE_OVERWINTER, BeforeOverwinter),
135 (OVERWINTER, Overwinter),
136 (SAPLING, Sapling),
137 (BLOSSOM, Blossom),
138 (HEARTWOOD, Heartwood),
139 (CANOPY, Canopy),
140 (NU5, Nu5),
141 (NU6, Nu6),
142 (NU6_1, Nu6_1),
143 (NU6_2, Nu6_2),
144 (NU6_3, Nu6_3),
145 ]
146};
147
148#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
151pub struct ConsensusBranchId(pub(crate) u32);
152
153impl BytesInDisplayOrder<false, 4> for ConsensusBranchId {
154 fn bytes_in_serialized_order(&self) -> [u8; 4] {
155 self.0.to_be_bytes()
156 }
157
158 fn from_bytes_in_serialized_order(bytes: [u8; 4]) -> Self {
159 ConsensusBranchId(u32::from_be_bytes(bytes))
160 }
161}
162
163impl From<ConsensusBranchId> for u32 {
164 fn from(branch: ConsensusBranchId) -> u32 {
165 branch.0
166 }
167}
168
169impl From<u32> for ConsensusBranchId {
170 fn from(branch: u32) -> Self {
171 ConsensusBranchId(branch)
172 }
173}
174
175impl ToHex for &ConsensusBranchId {
176 fn encode_hex<T: FromIterator<char>>(&self) -> T {
177 self.bytes_in_display_order().encode_hex()
178 }
179
180 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
181 self.bytes_in_display_order().encode_hex_upper()
182 }
183}
184
185impl ToHex for ConsensusBranchId {
186 fn encode_hex<T: FromIterator<char>>(&self) -> T {
187 self.bytes_in_display_order().encode_hex()
188 }
189
190 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
191 self.bytes_in_display_order().encode_hex_upper()
192 }
193}
194
195impl FromHex for ConsensusBranchId {
196 type Error = <[u8; 4] as FromHex>::Error;
197
198 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
199 let branch = <[u8; 4]>::from_hex(hex)?;
200 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
201 }
202}
203
204impl fmt::Display for ConsensusBranchId {
205 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206 f.write_str(&self.encode_hex::<String>())
207 }
208}
209
210impl TryFrom<ConsensusBranchId> for zcash_protocol::consensus::BranchId {
211 type Error = crate::Error;
212
213 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
214 zcash_protocol::consensus::BranchId::try_from(u32::from(id))
215 .map_err(|_| Self::Error::InvalidConsensusBranchId)
216 }
217}
218
219pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
230 (Overwinter, ConsensusBranchId(0x5ba81b19)),
231 (Sapling, ConsensusBranchId(0x76b809bb)),
232 (Blossom, ConsensusBranchId(0x2bb40e60)),
233 (Heartwood, ConsensusBranchId(0xf5b9230b)),
234 (Canopy, ConsensusBranchId(0xe9ff75a6)),
235 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
236 (Nu6, ConsensusBranchId(0xc8e71055)),
237 (Nu6_1, ConsensusBranchId(0x4dec4df0)),
238 (Nu6_2, ConsensusBranchId(0x5437f330)),
239 (Nu6_3, ConsensusBranchId(0x37a5165b)),
241 #[cfg(any(test, feature = "zebra-test"))]
243 (Nu7, ConsensusBranchId(0xfffffffe)),
244 #[cfg(zcash_unstable = "zfuture")]
248 (ZFuture, ConsensusBranchId(0xfffffffd)),
249];
250
251const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
253
254pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
256
257pub const POW_AVERAGING_WINDOW: usize = 17;
261
262const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
267
268const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
272
273pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
278
279impl Network {
280 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
291 match self {
292 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
293 Testnet(params) => params.activation_heights().clone(),
294 }
295 }
296
297 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
300 NetworkUpgrade::iter()
301 .filter_map(|nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
302 .collect()
303 }
304}
305
306impl NetworkUpgrade {
307 pub fn current_with_activation_height(
309 network: &Network,
310 height: block::Height,
311 ) -> (NetworkUpgrade, block::Height) {
312 network
313 .activation_list()
314 .range(..=height)
315 .map(|(&h, &nu)| (nu, h))
316 .next_back()
317 .expect("every height has a current network upgrade")
318 }
319
320 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
322 network
323 .activation_list()
324 .range(..=height)
325 .map(|(_, nu)| *nu)
326 .next_back()
327 .expect("every height has a current network upgrade")
328 }
329
330 pub fn next_upgrade(self) -> Option<Self> {
332 Self::iter().skip_while(|&nu| self != nu).nth(1)
333 }
334
335 pub fn previous_upgrade(self) -> Option<Self> {
337 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
338 }
339
340 #[cfg(test)]
345 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
346 use std::ops::Bound::*;
347
348 network
349 .activation_list()
350 .range((Excluded(height), Unbounded))
351 .map(|(_, nu)| *nu)
352 .next()
353 }
354
355 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
367 network
368 .activation_list()
369 .iter()
370 .find(|(_, nu)| nu == &self)
371 .map(|(height, _)| *height)
372 .or_else(|| {
373 self.next_upgrade()
374 .and_then(|next_nu| next_nu.activation_height(network))
375 })
376 }
377
378 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
384 network.activation_list().contains_key(&height)
385 }
386
387 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
396 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
397 }
398
399 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
403 NetworkUpgrade::branch_id_list().get(self).cloned()
404 }
405
406 pub fn target_spacing(&self) -> Duration {
411 let spacing_seconds = match self {
412 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
413 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu6_2 | Nu6_3 | Nu7 => {
414 POST_BLOSSOM_POW_TARGET_SPACING.into()
415 }
416
417 #[cfg(zcash_unstable = "zfuture")]
418 ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
419 };
420
421 Duration::seconds(spacing_seconds)
422 }
423
424 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
428 NetworkUpgrade::current(network, height).target_spacing()
429 }
430
431 pub fn target_spacings(
433 network: &Network,
434 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
435 [
436 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
437 (
438 NetworkUpgrade::Blossom,
439 POST_BLOSSOM_POW_TARGET_SPACING.into(),
440 ),
441 ]
442 .into_iter()
443 .filter_map(move |(upgrade, spacing_seconds)| {
444 let activation_height = upgrade.activation_height(network)?;
445 let target_spacing = Duration::seconds(spacing_seconds);
446 Some((activation_height, target_spacing))
447 })
448 }
449
450 pub fn minimum_difficulty_spacing_for_height(
455 network: &Network,
456 height: block::Height,
457 ) -> Option<Duration> {
458 match (network, height) {
459 (Network::Testnet(_params), height)
461 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
462 {
463 None
464 }
465 (Network::Mainnet, _) => None,
466 (Network::Testnet(_params), _) => {
467 let network_upgrade = NetworkUpgrade::current(network, height);
468 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
469 }
470 }
471 }
472
473 pub fn is_testnet_min_difficulty_block(
489 network: &Network,
490 block_height: block::Height,
491 block_time: DateTime<Utc>,
492 previous_block_time: DateTime<Utc>,
493 ) -> bool {
494 let block_time_gap = block_time - previous_block_time;
495 if let Some(min_difficulty_gap) =
496 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
497 {
498 block_time_gap > min_difficulty_gap
499 } else {
500 false
501 }
502 }
503
504 pub fn averaging_window_timespan(&self) -> Duration {
508 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
509 }
510
511 pub fn averaging_window_timespan_for_height(
515 network: &Network,
516 height: block::Height,
517 ) -> Duration {
518 NetworkUpgrade::current(network, height).averaging_window_timespan()
519 }
520
521 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
523 <Self as IntoEnumIterator>::iter()
524 }
525}
526
527impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
528 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
529 match nu {
530 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
531 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
532 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
533 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
534 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
535 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
536 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
537 zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
538 zcash_protocol::consensus::NetworkUpgrade::Nu6_2 => Self::Nu6_2,
539 zcash_protocol::consensus::NetworkUpgrade::Nu6_3 => Self::Nu6_3,
540 #[cfg(zcash_unstable = "nu7")]
541 zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
542 #[cfg(zcash_unstable = "zfuture")]
543 zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
544 }
545 }
546}
547
548impl ConsensusBranchId {
549 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
558
559 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
563 NetworkUpgrade::current(network, height).branch_id()
564 }
565}