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 (NU6_3, Nu6_3),
119 ]
120};
121#[allow(unused)]
131pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
132 use super::constants::activation_heights::testnet::*;
133 &[
134 (block::Height(0), Genesis),
135 (BEFORE_OVERWINTER, BeforeOverwinter),
136 (OVERWINTER, Overwinter),
137 (SAPLING, Sapling),
138 (BLOSSOM, Blossom),
139 (HEARTWOOD, Heartwood),
140 (CANOPY, Canopy),
141 (NU5, Nu5),
142 (NU6, Nu6),
143 (NU6_1, Nu6_1),
144 (NU6_2, Nu6_2),
145 (NU6_3, Nu6_3),
146 ]
147};
148
149#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
152pub struct ConsensusBranchId(pub(crate) u32);
153
154impl BytesInDisplayOrder<false, 4> for ConsensusBranchId {
155 fn bytes_in_serialized_order(&self) -> [u8; 4] {
156 self.0.to_be_bytes()
157 }
158
159 fn from_bytes_in_serialized_order(bytes: [u8; 4]) -> Self {
160 ConsensusBranchId(u32::from_be_bytes(bytes))
161 }
162}
163
164impl From<ConsensusBranchId> for u32 {
165 fn from(branch: ConsensusBranchId) -> u32 {
166 branch.0
167 }
168}
169
170impl From<u32> for ConsensusBranchId {
171 fn from(branch: u32) -> Self {
172 ConsensusBranchId(branch)
173 }
174}
175
176impl ToHex for &ConsensusBranchId {
177 fn encode_hex<T: FromIterator<char>>(&self) -> T {
178 self.bytes_in_display_order().encode_hex()
179 }
180
181 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
182 self.bytes_in_display_order().encode_hex_upper()
183 }
184}
185
186impl ToHex for ConsensusBranchId {
187 fn encode_hex<T: FromIterator<char>>(&self) -> T {
188 self.bytes_in_display_order().encode_hex()
189 }
190
191 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
192 self.bytes_in_display_order().encode_hex_upper()
193 }
194}
195
196impl FromHex for ConsensusBranchId {
197 type Error = <[u8; 4] as FromHex>::Error;
198
199 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
200 let branch = <[u8; 4]>::from_hex(hex)?;
201 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
202 }
203}
204
205impl fmt::Display for ConsensusBranchId {
206 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207 f.write_str(&self.encode_hex::<String>())
208 }
209}
210
211impl TryFrom<ConsensusBranchId> for zcash_protocol::consensus::BranchId {
212 type Error = crate::Error;
213
214 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
215 zcash_protocol::consensus::BranchId::try_from(u32::from(id))
216 .map_err(|_| Self::Error::InvalidConsensusBranchId)
217 }
218}
219
220pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
231 (Overwinter, ConsensusBranchId(0x5ba81b19)),
232 (Sapling, ConsensusBranchId(0x76b809bb)),
233 (Blossom, ConsensusBranchId(0x2bb40e60)),
234 (Heartwood, ConsensusBranchId(0xf5b9230b)),
235 (Canopy, ConsensusBranchId(0xe9ff75a6)),
236 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
237 (Nu6, ConsensusBranchId(0xc8e71055)),
238 (Nu6_1, ConsensusBranchId(0x4dec4df0)),
239 (Nu6_2, ConsensusBranchId(0x5437f330)),
240 (Nu6_3, ConsensusBranchId(0x37a5165b)),
242 #[cfg(any(test, feature = "zebra-test"))]
244 (Nu7, ConsensusBranchId(0xfffffffe)),
245 #[cfg(zcash_unstable = "zfuture")]
249 (ZFuture, ConsensusBranchId(0xfffffffd)),
250];
251
252const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
254
255pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
257
258pub const POW_AVERAGING_WINDOW: usize = 17;
262
263const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
268
269const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
273
274pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
279
280impl Network {
281 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
292 match self {
293 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
294 Testnet(params) => params.activation_heights().clone(),
295 }
296 }
297
298 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
301 NetworkUpgrade::iter()
302 .filter_map(|nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
303 .collect()
304 }
305}
306
307impl NetworkUpgrade {
308 pub fn current_with_activation_height(
310 network: &Network,
311 height: block::Height,
312 ) -> (NetworkUpgrade, block::Height) {
313 network
314 .activation_list()
315 .range(..=height)
316 .map(|(&h, &nu)| (nu, h))
317 .next_back()
318 .expect("every height has a current network upgrade")
319 }
320
321 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
323 network
324 .activation_list()
325 .range(..=height)
326 .map(|(_, nu)| *nu)
327 .next_back()
328 .expect("every height has a current network upgrade")
329 }
330
331 pub fn next_upgrade(self) -> Option<Self> {
333 Self::iter().skip_while(|&nu| self != nu).nth(1)
334 }
335
336 pub fn previous_upgrade(self) -> Option<Self> {
338 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
339 }
340
341 #[cfg(test)]
346 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
347 use std::ops::Bound::*;
348
349 network
350 .activation_list()
351 .range((Excluded(height), Unbounded))
352 .map(|(_, nu)| *nu)
353 .next()
354 }
355
356 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
368 network
369 .activation_list()
370 .iter()
371 .find(|(_, nu)| nu == &self)
372 .map(|(height, _)| *height)
373 .or_else(|| {
374 self.next_upgrade()
375 .and_then(|next_nu| next_nu.activation_height(network))
376 })
377 }
378
379 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
385 network.activation_list().contains_key(&height)
386 }
387
388 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
397 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
398 }
399
400 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
404 NetworkUpgrade::branch_id_list().get(self).cloned()
405 }
406
407 pub fn target_spacing(&self) -> Duration {
412 let spacing_seconds = match self {
413 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
414 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu6_2 | Nu6_3 | Nu7 => {
415 POST_BLOSSOM_POW_TARGET_SPACING.into()
416 }
417
418 #[cfg(zcash_unstable = "zfuture")]
419 ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
420 };
421
422 Duration::seconds(spacing_seconds)
423 }
424
425 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
429 NetworkUpgrade::current(network, height).target_spacing()
430 }
431
432 pub fn target_spacings(
434 network: &Network,
435 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
436 [
437 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
438 (
439 NetworkUpgrade::Blossom,
440 POST_BLOSSOM_POW_TARGET_SPACING.into(),
441 ),
442 ]
443 .into_iter()
444 .filter_map(move |(upgrade, spacing_seconds)| {
445 let activation_height = upgrade.activation_height(network)?;
446 let target_spacing = Duration::seconds(spacing_seconds);
447 Some((activation_height, target_spacing))
448 })
449 }
450
451 pub fn minimum_difficulty_spacing_for_height(
456 network: &Network,
457 height: block::Height,
458 ) -> Option<Duration> {
459 match (network, height) {
460 (Network::Testnet(_params), height)
462 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
463 {
464 None
465 }
466 (Network::Mainnet, _) => None,
467 (Network::Testnet(_params), _) => {
468 let network_upgrade = NetworkUpgrade::current(network, height);
469 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
470 }
471 }
472 }
473
474 pub fn is_testnet_min_difficulty_block(
490 network: &Network,
491 block_height: block::Height,
492 block_time: DateTime<Utc>,
493 previous_block_time: DateTime<Utc>,
494 ) -> bool {
495 let block_time_gap = block_time - previous_block_time;
496 if let Some(min_difficulty_gap) =
497 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
498 {
499 block_time_gap > min_difficulty_gap
500 } else {
501 false
502 }
503 }
504
505 pub fn averaging_window_timespan(&self) -> Duration {
509 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
510 }
511
512 pub fn averaging_window_timespan_for_height(
516 network: &Network,
517 height: block::Height,
518 ) -> Duration {
519 NetworkUpgrade::current(network, height).averaging_window_timespan()
520 }
521
522 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
524 <Self as IntoEnumIterator>::iter()
525 }
526}
527
528impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
529 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
530 match nu {
531 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
532 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
533 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
534 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
535 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
536 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
537 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
538 zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
539 zcash_protocol::consensus::NetworkUpgrade::Nu6_2 => Self::Nu6_2,
540 zcash_protocol::consensus::NetworkUpgrade::Nu6_3 => Self::Nu6_3,
541 #[cfg(zcash_unstable = "nu7")]
542 zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
543 #[cfg(zcash_unstable = "zfuture")]
544 zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
545 }
546 }
547}
548
549impl ConsensusBranchId {
550 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
559
560 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
564 NetworkUpgrade::current(network, height).branch_id()
565 }
566}