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 = "NU7")]
66 Nu7,
67
68 #[cfg(zcash_unstable = "zfuture")]
69 ZFuture,
70}
71
72impl TryFrom<u32> for NetworkUpgrade {
73 type Error = crate::Error;
74
75 fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
76 CONSENSUS_BRANCH_IDS
77 .iter()
78 .find(|id| id.1 == ConsensusBranchId(branch_id))
79 .map(|nu| nu.0)
80 .ok_or(Self::Error::InvalidConsensusBranchId)
81 }
82}
83
84impl fmt::Display for NetworkUpgrade {
85 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86 fmt::Debug::fmt(self, f)
88 }
89}
90
91#[allow(unused)]
101pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
102 use super::constants::activation_heights::mainnet::*;
103 &[
104 (block::Height(0), Genesis),
105 (BEFORE_OVERWINTER, BeforeOverwinter),
106 (OVERWINTER, Overwinter),
107 (SAPLING, Sapling),
108 (BLOSSOM, Blossom),
109 (HEARTWOOD, Heartwood),
110 (CANOPY, Canopy),
111 (NU5, Nu5),
112 (NU6, Nu6),
113 (NU6_1, Nu6_1),
114 (NU6_2, Nu6_2),
115 ]
116};
117#[allow(unused)]
127pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
128 use super::constants::activation_heights::testnet::*;
129 &[
130 (block::Height(0), Genesis),
131 (BEFORE_OVERWINTER, BeforeOverwinter),
132 (OVERWINTER, Overwinter),
133 (SAPLING, Sapling),
134 (BLOSSOM, Blossom),
135 (HEARTWOOD, Heartwood),
136 (CANOPY, Canopy),
137 (NU5, Nu5),
138 (NU6, Nu6),
139 (NU6_1, Nu6_1),
140 (NU6_2, Nu6_2),
141 ]
142};
143
144#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
147pub struct ConsensusBranchId(pub(crate) u32);
148
149impl BytesInDisplayOrder<false, 4> for ConsensusBranchId {
150 fn bytes_in_serialized_order(&self) -> [u8; 4] {
151 self.0.to_be_bytes()
152 }
153
154 fn from_bytes_in_serialized_order(bytes: [u8; 4]) -> Self {
155 ConsensusBranchId(u32::from_be_bytes(bytes))
156 }
157}
158
159impl From<ConsensusBranchId> for u32 {
160 fn from(branch: ConsensusBranchId) -> u32 {
161 branch.0
162 }
163}
164
165impl From<u32> for ConsensusBranchId {
166 fn from(branch: u32) -> Self {
167 ConsensusBranchId(branch)
168 }
169}
170
171impl ToHex for &ConsensusBranchId {
172 fn encode_hex<T: FromIterator<char>>(&self) -> T {
173 self.bytes_in_display_order().encode_hex()
174 }
175
176 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
177 self.bytes_in_display_order().encode_hex_upper()
178 }
179}
180
181impl ToHex for ConsensusBranchId {
182 fn encode_hex<T: FromIterator<char>>(&self) -> T {
183 self.bytes_in_display_order().encode_hex()
184 }
185
186 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
187 self.bytes_in_display_order().encode_hex_upper()
188 }
189}
190
191impl FromHex for ConsensusBranchId {
192 type Error = <[u8; 4] as FromHex>::Error;
193
194 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
195 let branch = <[u8; 4]>::from_hex(hex)?;
196 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
197 }
198}
199
200impl fmt::Display for ConsensusBranchId {
201 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202 f.write_str(&self.encode_hex::<String>())
203 }
204}
205
206impl TryFrom<ConsensusBranchId> for zcash_protocol::consensus::BranchId {
207 type Error = crate::Error;
208
209 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
210 zcash_protocol::consensus::BranchId::try_from(u32::from(id))
211 .map_err(|_| Self::Error::InvalidConsensusBranchId)
212 }
213}
214
215pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
226 (Overwinter, ConsensusBranchId(0x5ba81b19)),
227 (Sapling, ConsensusBranchId(0x76b809bb)),
228 (Blossom, ConsensusBranchId(0x2bb40e60)),
229 (Heartwood, ConsensusBranchId(0xf5b9230b)),
230 (Canopy, ConsensusBranchId(0xe9ff75a6)),
231 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
232 (Nu6, ConsensusBranchId(0xc8e71055)),
233 (Nu6_1, ConsensusBranchId(0x4dec4df0)),
234 (Nu6_2, ConsensusBranchId(0x5437f330)),
235 #[cfg(any(test, feature = "zebra-test"))]
237 (Nu7, ConsensusBranchId(0xffffffff)),
238 #[cfg(zcash_unstable = "zfuture")]
239 (ZFuture, ConsensusBranchId(0xffffffff)),
240];
241
242const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
244
245pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
247
248pub const POW_AVERAGING_WINDOW: usize = 17;
252
253const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
258
259const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
263
264pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
269
270impl Network {
271 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
282 match self {
283 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
284 Testnet(params) => params.activation_heights().clone(),
285 }
286 }
287
288 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
291 NetworkUpgrade::iter()
292 .filter_map(|nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
293 .collect()
294 }
295}
296
297impl NetworkUpgrade {
298 pub fn current_with_activation_height(
300 network: &Network,
301 height: block::Height,
302 ) -> (NetworkUpgrade, block::Height) {
303 network
304 .activation_list()
305 .range(..=height)
306 .map(|(&h, &nu)| (nu, h))
307 .next_back()
308 .expect("every height has a current network upgrade")
309 }
310
311 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
313 network
314 .activation_list()
315 .range(..=height)
316 .map(|(_, nu)| *nu)
317 .next_back()
318 .expect("every height has a current network upgrade")
319 }
320
321 pub fn next_upgrade(self) -> Option<Self> {
323 Self::iter().skip_while(|&nu| self != nu).nth(1)
324 }
325
326 pub fn previous_upgrade(self) -> Option<Self> {
328 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
329 }
330
331 #[cfg(test)]
336 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
337 use std::ops::Bound::*;
338
339 network
340 .activation_list()
341 .range((Excluded(height), Unbounded))
342 .map(|(_, nu)| *nu)
343 .next()
344 }
345
346 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
358 network
359 .activation_list()
360 .iter()
361 .find(|(_, nu)| nu == &self)
362 .map(|(height, _)| *height)
363 .or_else(|| {
364 self.next_upgrade()
365 .and_then(|next_nu| next_nu.activation_height(network))
366 })
367 }
368
369 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
375 network.activation_list().contains_key(&height)
376 }
377
378 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
387 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
388 }
389
390 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
394 NetworkUpgrade::branch_id_list().get(self).cloned()
395 }
396
397 pub fn target_spacing(&self) -> Duration {
402 let spacing_seconds = match self {
403 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
404 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu6_2 | Nu7 => {
405 POST_BLOSSOM_POW_TARGET_SPACING.into()
406 }
407
408 #[cfg(zcash_unstable = "zfuture")]
409 ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
410 };
411
412 Duration::seconds(spacing_seconds)
413 }
414
415 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
419 NetworkUpgrade::current(network, height).target_spacing()
420 }
421
422 pub fn target_spacings(
424 network: &Network,
425 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
426 [
427 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
428 (
429 NetworkUpgrade::Blossom,
430 POST_BLOSSOM_POW_TARGET_SPACING.into(),
431 ),
432 ]
433 .into_iter()
434 .filter_map(move |(upgrade, spacing_seconds)| {
435 let activation_height = upgrade.activation_height(network)?;
436 let target_spacing = Duration::seconds(spacing_seconds);
437 Some((activation_height, target_spacing))
438 })
439 }
440
441 pub fn minimum_difficulty_spacing_for_height(
446 network: &Network,
447 height: block::Height,
448 ) -> Option<Duration> {
449 match (network, height) {
450 (Network::Testnet(_params), height)
452 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
453 {
454 None
455 }
456 (Network::Mainnet, _) => None,
457 (Network::Testnet(_params), _) => {
458 let network_upgrade = NetworkUpgrade::current(network, height);
459 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
460 }
461 }
462 }
463
464 pub fn is_testnet_min_difficulty_block(
480 network: &Network,
481 block_height: block::Height,
482 block_time: DateTime<Utc>,
483 previous_block_time: DateTime<Utc>,
484 ) -> bool {
485 let block_time_gap = block_time - previous_block_time;
486 if let Some(min_difficulty_gap) =
487 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
488 {
489 block_time_gap > min_difficulty_gap
490 } else {
491 false
492 }
493 }
494
495 pub fn averaging_window_timespan(&self) -> Duration {
499 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
500 }
501
502 pub fn averaging_window_timespan_for_height(
506 network: &Network,
507 height: block::Height,
508 ) -> Duration {
509 NetworkUpgrade::current(network, height).averaging_window_timespan()
510 }
511
512 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
514 <Self as IntoEnumIterator>::iter()
515 }
516}
517
518impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
519 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
520 match nu {
521 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
522 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
523 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
524 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
525 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
526 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
527 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
528 zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
529 zcash_protocol::consensus::NetworkUpgrade::Nu6_2 => Self::Nu6_2,
530 #[cfg(zcash_unstable = "nu7")]
531 zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
532 #[cfg(zcash_unstable = "zfuture")]
533 zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
534 }
535 }
536}
537
538impl ConsensusBranchId {
539 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
548
549 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
553 NetworkUpgrade::current(network, height).branch_id()
554 }
555}