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
15#[cfg(any(test, feature = "proptest-impl"))]
16use proptest_derive::Arbitrary;
17
18const NETWORK_UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[
20 Genesis,
21 BeforeOverwinter,
22 Overwinter,
23 Sapling,
24 Blossom,
25 Heartwood,
26 Canopy,
27 Nu5,
28 Nu6,
29 Nu6_1,
30 #[cfg(any(test, feature = "zebra-test"))]
31 Nu7,
32];
33
34#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
39#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
40pub enum NetworkUpgrade {
41 Genesis,
47 BeforeOverwinter,
52 Overwinter,
54 Sapling,
56 Blossom,
58 Heartwood,
60 Canopy,
62 #[serde(rename = "NU5")]
64 Nu5,
65 #[serde(rename = "NU6")]
67 Nu6,
68 #[serde(rename = "NU6.1")]
70 Nu6_1,
71 #[serde(rename = "NU7")]
73 Nu7,
74
75 #[cfg(zcash_unstable = "zfuture")]
76 ZFuture,
77}
78
79impl TryFrom<u32> for NetworkUpgrade {
80 type Error = crate::Error;
81
82 fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
83 CONSENSUS_BRANCH_IDS
84 .iter()
85 .find(|id| id.1 == ConsensusBranchId(branch_id))
86 .map(|nu| nu.0)
87 .ok_or(Self::Error::InvalidConsensusBranchId)
88 }
89}
90
91impl fmt::Display for NetworkUpgrade {
92 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 fmt::Debug::fmt(self, f)
95 }
96}
97
98#[allow(unused)]
108pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
109 use super::constants::activation_heights::mainnet::*;
110 &[
111 (block::Height(0), Genesis),
112 (BEFORE_OVERWINTER, BeforeOverwinter),
113 (OVERWINTER, Overwinter),
114 (SAPLING, Sapling),
115 (BLOSSOM, Blossom),
116 (HEARTWOOD, Heartwood),
117 (CANOPY, Canopy),
118 (NU5, Nu5),
119 (NU6, Nu6),
120 (NU6_1, Nu6_1),
121 ]
122};
123#[allow(unused)]
133pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = {
134 use super::constants::activation_heights::testnet::*;
135 &[
136 (block::Height(0), Genesis),
137 (BEFORE_OVERWINTER, BeforeOverwinter),
138 (OVERWINTER, Overwinter),
139 (SAPLING, Sapling),
140 (BLOSSOM, Blossom),
141 (HEARTWOOD, Heartwood),
142 (CANOPY, Canopy),
143 (NU5, Nu5),
144 (NU6, Nu6),
145 (NU6_1, Nu6_1),
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_primitives::consensus::BranchId {
212 type Error = crate::Error;
213
214 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
215 zcash_primitives::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 #[cfg(any(test, feature = "zebra-test"))]
241 (Nu7, ConsensusBranchId(0xffffffff)),
242 #[cfg(zcash_unstable = "zfuture")]
243 (ZFuture, ConsensusBranchId(0xffffffff)),
244];
245
246const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
248
249pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
251
252pub const POW_AVERAGING_WINDOW: usize = 17;
256
257const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
262
263const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
267
268pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
273
274impl Network {
275 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
286 match self {
287 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
288 Testnet(params) => params.activation_heights().clone(),
289 }
290 }
291
292 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
295 NETWORK_UPGRADES_IN_ORDER
296 .iter()
297 .map_while(|&nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
298 .collect()
299 }
300}
301
302impl NetworkUpgrade {
303 pub fn current_with_activation_height(
305 network: &Network,
306 height: block::Height,
307 ) -> (NetworkUpgrade, block::Height) {
308 network
309 .activation_list()
310 .range(..=height)
311 .map(|(&h, &nu)| (nu, h))
312 .next_back()
313 .expect("every height has a current network upgrade")
314 }
315
316 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
318 network
319 .activation_list()
320 .range(..=height)
321 .map(|(_, nu)| *nu)
322 .next_back()
323 .expect("every height has a current network upgrade")
324 }
325
326 pub fn next_upgrade(self) -> Option<Self> {
328 Self::iter().skip_while(|&nu| self != nu).nth(1)
329 }
330
331 pub fn previous_upgrade(self) -> Option<Self> {
333 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
334 }
335
336 #[cfg(test)]
341 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
342 use std::ops::Bound::*;
343
344 network
345 .activation_list()
346 .range((Excluded(height), Unbounded))
347 .map(|(_, nu)| *nu)
348 .next()
349 }
350
351 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
363 network
364 .activation_list()
365 .iter()
366 .find(|(_, nu)| nu == &self)
367 .map(|(height, _)| *height)
368 .or_else(|| {
369 self.next_upgrade()
370 .and_then(|next_nu| next_nu.activation_height(network))
371 })
372 }
373
374 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
380 network.activation_list().contains_key(&height)
381 }
382
383 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
392 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
393 }
394
395 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
399 NetworkUpgrade::branch_id_list().get(self).cloned()
400 }
401
402 pub fn target_spacing(&self) -> Duration {
407 let spacing_seconds = match self {
408 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
409 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu7 => {
410 POST_BLOSSOM_POW_TARGET_SPACING.into()
411 }
412
413 #[cfg(zcash_unstable = "zfuture")]
414 ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
415 };
416
417 Duration::seconds(spacing_seconds)
418 }
419
420 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
424 NetworkUpgrade::current(network, height).target_spacing()
425 }
426
427 pub fn target_spacings(
429 network: &Network,
430 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
431 [
432 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
433 (
434 NetworkUpgrade::Blossom,
435 POST_BLOSSOM_POW_TARGET_SPACING.into(),
436 ),
437 ]
438 .into_iter()
439 .filter_map(move |(upgrade, spacing_seconds)| {
440 let activation_height = upgrade.activation_height(network)?;
441 let target_spacing = Duration::seconds(spacing_seconds);
442 Some((activation_height, target_spacing))
443 })
444 }
445
446 pub fn minimum_difficulty_spacing_for_height(
451 network: &Network,
452 height: block::Height,
453 ) -> Option<Duration> {
454 match (network, height) {
455 (Network::Testnet(_params), height)
457 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
458 {
459 None
460 }
461 (Network::Mainnet, _) => None,
462 (Network::Testnet(_params), _) => {
463 let network_upgrade = NetworkUpgrade::current(network, height);
464 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
465 }
466 }
467 }
468
469 pub fn is_testnet_min_difficulty_block(
485 network: &Network,
486 block_height: block::Height,
487 block_time: DateTime<Utc>,
488 previous_block_time: DateTime<Utc>,
489 ) -> bool {
490 let block_time_gap = block_time - previous_block_time;
491 if let Some(min_difficulty_gap) =
492 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
493 {
494 block_time_gap > min_difficulty_gap
495 } else {
496 false
497 }
498 }
499
500 pub fn averaging_window_timespan(&self) -> Duration {
504 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
505 }
506
507 pub fn averaging_window_timespan_for_height(
511 network: &Network,
512 height: block::Height,
513 ) -> Duration {
514 NetworkUpgrade::current(network, height).averaging_window_timespan()
515 }
516
517 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
519 NETWORK_UPGRADES_IN_ORDER.iter().copied()
520 }
521}
522
523impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
524 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
525 match nu {
526 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
527 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
528 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
529 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
530 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
531 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
532 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
533 zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
534 #[cfg(zcash_unstable = "nu7")]
535 zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
536 #[cfg(zcash_unstable = "zfuture")]
537 zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
538 }
539 }
540}
541
542impl ConsensusBranchId {
543 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
552
553 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
557 NetworkUpgrade::current(network, height).branch_id()
558 }
559}