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 = "NU7")]
63 Nu7,
64
65 #[cfg(zcash_unstable = "zfuture")]
66 ZFuture,
67}
68
69impl TryFrom<u32> for NetworkUpgrade {
70 type Error = crate::Error;
71
72 fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
73 CONSENSUS_BRANCH_IDS
74 .iter()
75 .find(|id| id.1 == ConsensusBranchId(branch_id))
76 .map(|nu| nu.0)
77 .ok_or(Self::Error::InvalidConsensusBranchId)
78 }
79}
80
81impl fmt::Display for NetworkUpgrade {
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 fmt::Debug::fmt(self, f)
85 }
86}
87
88#[allow(unused)]
98pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
99 use super::constants::activation_heights::mainnet::*;
100 &[
101 (block::Height(0), Genesis),
102 (BEFORE_OVERWINTER, BeforeOverwinter),
103 (OVERWINTER, Overwinter),
104 (SAPLING, Sapling),
105 (BLOSSOM, Blossom),
106 (HEARTWOOD, Heartwood),
107 (CANOPY, Canopy),
108 (NU5, Nu5),
109 (NU6, Nu6),
110 (NU6_1, Nu6_1),
111 ]
112};
113#[allow(unused)]
123pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
124 use super::constants::activation_heights::testnet::*;
125 &[
126 (block::Height(0), Genesis),
127 (BEFORE_OVERWINTER, BeforeOverwinter),
128 (OVERWINTER, Overwinter),
129 (SAPLING, Sapling),
130 (BLOSSOM, Blossom),
131 (HEARTWOOD, Heartwood),
132 (CANOPY, Canopy),
133 (NU5, Nu5),
134 (NU6, Nu6),
135 (NU6_1, Nu6_1),
136 ]
137};
138
139#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
142pub struct ConsensusBranchId(pub(crate) u32);
143
144impl BytesInDisplayOrder<false, 4> for ConsensusBranchId {
145 fn bytes_in_serialized_order(&self) -> [u8; 4] {
146 self.0.to_be_bytes()
147 }
148
149 fn from_bytes_in_serialized_order(bytes: [u8; 4]) -> Self {
150 ConsensusBranchId(u32::from_be_bytes(bytes))
151 }
152}
153
154impl From<ConsensusBranchId> for u32 {
155 fn from(branch: ConsensusBranchId) -> u32 {
156 branch.0
157 }
158}
159
160impl From<u32> for ConsensusBranchId {
161 fn from(branch: u32) -> Self {
162 ConsensusBranchId(branch)
163 }
164}
165
166impl ToHex for &ConsensusBranchId {
167 fn encode_hex<T: FromIterator<char>>(&self) -> T {
168 self.bytes_in_display_order().encode_hex()
169 }
170
171 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
172 self.bytes_in_display_order().encode_hex_upper()
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 FromHex for ConsensusBranchId {
187 type Error = <[u8; 4] as FromHex>::Error;
188
189 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
190 let branch = <[u8; 4]>::from_hex(hex)?;
191 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
192 }
193}
194
195impl fmt::Display for ConsensusBranchId {
196 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197 f.write_str(&self.encode_hex::<String>())
198 }
199}
200
201impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
202 type Error = crate::Error;
203
204 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
205 zcash_primitives::consensus::BranchId::try_from(u32::from(id))
206 .map_err(|_| Self::Error::InvalidConsensusBranchId)
207 }
208}
209
210pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
221 (Overwinter, ConsensusBranchId(0x5ba81b19)),
222 (Sapling, ConsensusBranchId(0x76b809bb)),
223 (Blossom, ConsensusBranchId(0x2bb40e60)),
224 (Heartwood, ConsensusBranchId(0xf5b9230b)),
225 (Canopy, ConsensusBranchId(0xe9ff75a6)),
226 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
227 (Nu6, ConsensusBranchId(0xc8e71055)),
228 (Nu6_1, ConsensusBranchId(0x4dec4df0)),
229 #[cfg(any(test, feature = "zebra-test"))]
231 (Nu7, ConsensusBranchId(0xffffffff)),
232 #[cfg(zcash_unstable = "zfuture")]
233 (ZFuture, ConsensusBranchId(0xffffffff)),
234];
235
236const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
238
239pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
241
242pub const POW_AVERAGING_WINDOW: usize = 17;
246
247const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
252
253const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
257
258pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
263
264impl Network {
265 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
276 match self {
277 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
278 Testnet(params) => params.activation_heights().clone(),
279 }
280 }
281
282 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
285 NetworkUpgrade::iter()
286 .filter_map(|nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
287 .collect()
288 }
289}
290
291impl NetworkUpgrade {
292 pub fn current_with_activation_height(
294 network: &Network,
295 height: block::Height,
296 ) -> (NetworkUpgrade, block::Height) {
297 network
298 .activation_list()
299 .range(..=height)
300 .map(|(&h, &nu)| (nu, h))
301 .next_back()
302 .expect("every height has a current network upgrade")
303 }
304
305 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
307 network
308 .activation_list()
309 .range(..=height)
310 .map(|(_, nu)| *nu)
311 .next_back()
312 .expect("every height has a current network upgrade")
313 }
314
315 pub fn next_upgrade(self) -> Option<Self> {
317 Self::iter().skip_while(|&nu| self != nu).nth(1)
318 }
319
320 pub fn previous_upgrade(self) -> Option<Self> {
322 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
323 }
324
325 #[cfg(test)]
330 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
331 use std::ops::Bound::*;
332
333 network
334 .activation_list()
335 .range((Excluded(height), Unbounded))
336 .map(|(_, nu)| *nu)
337 .next()
338 }
339
340 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
352 network
353 .activation_list()
354 .iter()
355 .find(|(_, nu)| nu == &self)
356 .map(|(height, _)| *height)
357 .or_else(|| {
358 self.next_upgrade()
359 .and_then(|next_nu| next_nu.activation_height(network))
360 })
361 }
362
363 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
369 network.activation_list().contains_key(&height)
370 }
371
372 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
381 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
382 }
383
384 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
388 NetworkUpgrade::branch_id_list().get(self).cloned()
389 }
390
391 pub fn target_spacing(&self) -> Duration {
396 let spacing_seconds = match self {
397 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
398 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu7 => {
399 POST_BLOSSOM_POW_TARGET_SPACING.into()
400 }
401
402 #[cfg(zcash_unstable = "zfuture")]
403 ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
404 };
405
406 Duration::seconds(spacing_seconds)
407 }
408
409 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
413 NetworkUpgrade::current(network, height).target_spacing()
414 }
415
416 pub fn target_spacings(
418 network: &Network,
419 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
420 [
421 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
422 (
423 NetworkUpgrade::Blossom,
424 POST_BLOSSOM_POW_TARGET_SPACING.into(),
425 ),
426 ]
427 .into_iter()
428 .filter_map(move |(upgrade, spacing_seconds)| {
429 let activation_height = upgrade.activation_height(network)?;
430 let target_spacing = Duration::seconds(spacing_seconds);
431 Some((activation_height, target_spacing))
432 })
433 }
434
435 pub fn minimum_difficulty_spacing_for_height(
440 network: &Network,
441 height: block::Height,
442 ) -> Option<Duration> {
443 match (network, height) {
444 (Network::Testnet(_params), height)
446 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
447 {
448 None
449 }
450 (Network::Mainnet, _) => None,
451 (Network::Testnet(_params), _) => {
452 let network_upgrade = NetworkUpgrade::current(network, height);
453 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
454 }
455 }
456 }
457
458 pub fn is_testnet_min_difficulty_block(
474 network: &Network,
475 block_height: block::Height,
476 block_time: DateTime<Utc>,
477 previous_block_time: DateTime<Utc>,
478 ) -> bool {
479 let block_time_gap = block_time - previous_block_time;
480 if let Some(min_difficulty_gap) =
481 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
482 {
483 block_time_gap > min_difficulty_gap
484 } else {
485 false
486 }
487 }
488
489 pub fn averaging_window_timespan(&self) -> Duration {
493 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
494 }
495
496 pub fn averaging_window_timespan_for_height(
500 network: &Network,
501 height: block::Height,
502 ) -> Duration {
503 NetworkUpgrade::current(network, height).averaging_window_timespan()
504 }
505
506 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
508 <Self as IntoEnumIterator>::iter()
509 }
510}
511
512impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
513 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
514 match nu {
515 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
516 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
517 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
518 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
519 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
520 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
521 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
522 zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
523 #[cfg(zcash_unstable = "nu7")]
524 zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
525 #[cfg(zcash_unstable = "zfuture")]
526 zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
527 }
528 }
529}
530
531impl ConsensusBranchId {
532 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
541
542 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
546 NetworkUpgrade::current(network, height).branch_id()
547 }
548}