1use NetworkUpgrade::*;
4
5use crate::block;
6use crate::parameters::{Network, Network::*};
7
8use std::collections::{BTreeMap, HashMap};
9use std::fmt;
10
11use chrono::{DateTime, Duration, Utc};
12use hex::{FromHex, ToHex};
13
14#[cfg(any(test, feature = "proptest-impl"))]
15use proptest_derive::Arbitrary;
16
17const NETWORK_UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[
19 Genesis,
20 BeforeOverwinter,
21 Overwinter,
22 Sapling,
23 Blossom,
24 Heartwood,
25 Canopy,
26 Nu5,
27 Nu6,
28 Nu6_1,
29 #[cfg(any(test, feature = "zebra-test"))]
30 Nu7,
31];
32
33#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
38#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
39pub enum NetworkUpgrade {
40 Genesis,
46 BeforeOverwinter,
51 Overwinter,
53 Sapling,
55 Blossom,
57 Heartwood,
59 Canopy,
61 #[serde(rename = "NU5")]
63 Nu5,
64 #[serde(rename = "NU6")]
66 Nu6,
67 #[serde(rename = "NU6.1")]
69 Nu6_1,
70 #[serde(rename = "NU7")]
72 Nu7,
73
74 #[cfg(zcash_unstable = "zfuture")]
75 ZFuture,
76}
77
78impl TryFrom<u32> for NetworkUpgrade {
79 type Error = crate::Error;
80
81 fn try_from(branch_id: u32) -> Result<Self, Self::Error> {
82 CONSENSUS_BRANCH_IDS
83 .iter()
84 .find(|id| id.1 == ConsensusBranchId(branch_id))
85 .map(|nu| nu.0)
86 .ok_or(Self::Error::InvalidConsensusBranchId)
87 }
88}
89
90impl fmt::Display for NetworkUpgrade {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 fmt::Debug::fmt(self, f)
94 }
95}
96
97#[allow(unused)]
107pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
108 (block::Height(0), Genesis),
109 (block::Height(1), BeforeOverwinter),
110 (block::Height(347_500), Overwinter),
111 (block::Height(419_200), Sapling),
112 (block::Height(653_600), Blossom),
113 (block::Height(903_000), Heartwood),
114 (block::Height(1_046_400), Canopy),
115 (block::Height(1_687_104), Nu5),
116 (block::Height(2_726_400), Nu6),
117];
118
119pub const NU6_1_ACTIVATION_HEIGHT_TESTNET: block::Height = block::Height(3_536_500);
123
124#[allow(unused)]
134pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
135 (block::Height(0), Genesis),
136 (block::Height(1), BeforeOverwinter),
137 (block::Height(207_500), Overwinter),
138 (block::Height(280_000), Sapling),
139 (block::Height(584_000), Blossom),
140 (block::Height(903_800), Heartwood),
141 (block::Height(1_028_500), Canopy),
142 (block::Height(1_842_420), Nu5),
143 (block::Height(2_976_000), Nu6),
144 (NU6_1_ACTIVATION_HEIGHT_TESTNET, Nu6_1),
145];
146
147#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
150pub struct ConsensusBranchId(pub(crate) u32);
151
152impl ConsensusBranchId {
153 fn bytes_in_display_order(&self) -> [u8; 4] {
158 self.0.to_be_bytes()
159 }
160}
161
162impl From<ConsensusBranchId> for u32 {
163 fn from(branch: ConsensusBranchId) -> u32 {
164 branch.0
165 }
166}
167
168impl From<u32> for ConsensusBranchId {
169 fn from(branch: u32) -> Self {
170 ConsensusBranchId(branch)
171 }
172}
173
174impl ToHex for &ConsensusBranchId {
175 fn encode_hex<T: FromIterator<char>>(&self) -> T {
176 self.bytes_in_display_order().encode_hex()
177 }
178
179 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
180 self.bytes_in_display_order().encode_hex_upper()
181 }
182}
183
184impl ToHex for ConsensusBranchId {
185 fn encode_hex<T: FromIterator<char>>(&self) -> T {
186 self.bytes_in_display_order().encode_hex()
187 }
188
189 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
190 self.bytes_in_display_order().encode_hex_upper()
191 }
192}
193
194impl FromHex for ConsensusBranchId {
195 type Error = <[u8; 4] as FromHex>::Error;
196
197 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
198 let branch = <[u8; 4]>::from_hex(hex)?;
199 Ok(ConsensusBranchId(u32::from_be_bytes(branch)))
200 }
201}
202
203impl fmt::Display for ConsensusBranchId {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 f.write_str(&self.encode_hex::<String>())
206 }
207}
208
209impl TryFrom<ConsensusBranchId> for zcash_primitives::consensus::BranchId {
210 type Error = crate::Error;
211
212 fn try_from(id: ConsensusBranchId) -> Result<Self, Self::Error> {
213 zcash_primitives::consensus::BranchId::try_from(u32::from(id))
214 .map_err(|_| Self::Error::InvalidConsensusBranchId)
215 }
216}
217
218pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = &[
229 (Overwinter, ConsensusBranchId(0x5ba81b19)),
230 (Sapling, ConsensusBranchId(0x76b809bb)),
231 (Blossom, ConsensusBranchId(0x2bb40e60)),
232 (Heartwood, ConsensusBranchId(0xf5b9230b)),
233 (Canopy, ConsensusBranchId(0xe9ff75a6)),
234 (Nu5, ConsensusBranchId(0xc2d6d0b4)),
235 (Nu6, ConsensusBranchId(0xc8e71055)),
236 (Nu6_1, ConsensusBranchId(0x4dec4df0)),
237 #[cfg(any(test, feature = "zebra-test"))]
238 (Nu7, ConsensusBranchId(0x77190ad8)),
239 #[cfg(zcash_unstable = "zfuture")]
240 (ZFuture, ConsensusBranchId(0xffffffff)),
241];
242
243const PRE_BLOSSOM_POW_TARGET_SPACING: i64 = 150;
245
246pub const POST_BLOSSOM_POW_TARGET_SPACING: u32 = 75;
248
249pub const POW_AVERAGING_WINDOW: usize = 17;
253
254const TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER: i32 = 6;
259
260const TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT: block::Height = block::Height(299_188);
264
265pub const TESTNET_MAX_TIME_START_HEIGHT: block::Height = block::Height(653_606);
270
271impl Network {
272 pub fn activation_list(&self) -> BTreeMap<block::Height, NetworkUpgrade> {
283 match self {
284 Mainnet => MAINNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
285 Testnet(params) => params.activation_heights().clone(),
286 }
287 }
288
289 pub fn full_activation_list(&self) -> Vec<(block::Height, NetworkUpgrade)> {
292 NETWORK_UPGRADES_IN_ORDER
293 .iter()
294 .map_while(|&nu| Some((NetworkUpgrade::activation_height(&nu, self)?, nu)))
295 .collect()
296 }
297}
298
299impl NetworkUpgrade {
300 pub fn current_with_activation_height(
302 network: &Network,
303 height: block::Height,
304 ) -> (NetworkUpgrade, block::Height) {
305 network
306 .activation_list()
307 .range(..=height)
308 .map(|(&h, &nu)| (nu, h))
309 .next_back()
310 .expect("every height has a current network upgrade")
311 }
312
313 pub fn current(network: &Network, height: block::Height) -> NetworkUpgrade {
315 network
316 .activation_list()
317 .range(..=height)
318 .map(|(_, nu)| *nu)
319 .next_back()
320 .expect("every height has a current network upgrade")
321 }
322
323 pub fn next_upgrade(self) -> Option<Self> {
325 Self::iter().skip_while(|&nu| self != nu).nth(1)
326 }
327
328 pub fn previous_upgrade(self) -> Option<Self> {
330 Self::iter().rev().skip_while(|&nu| self != nu).nth(1)
331 }
332
333 #[cfg(test)]
338 pub fn next(network: &Network, height: block::Height) -> Option<NetworkUpgrade> {
339 use std::ops::Bound::*;
340
341 network
342 .activation_list()
343 .range((Excluded(height), Unbounded))
344 .map(|(_, nu)| *nu)
345 .next()
346 }
347
348 pub fn activation_height(&self, network: &Network) -> Option<block::Height> {
360 network
361 .activation_list()
362 .iter()
363 .find(|(_, nu)| nu == &self)
364 .map(|(height, _)| *height)
365 .or_else(|| {
366 self.next_upgrade()
367 .and_then(|next_nu| next_nu.activation_height(network))
368 })
369 }
370
371 pub fn is_activation_height(network: &Network, height: block::Height) -> bool {
377 network.activation_list().contains_key(&height)
378 }
379
380 pub(crate) fn branch_id_list() -> HashMap<NetworkUpgrade, ConsensusBranchId> {
389 CONSENSUS_BRANCH_IDS.iter().cloned().collect()
390 }
391
392 pub fn branch_id(&self) -> Option<ConsensusBranchId> {
396 NetworkUpgrade::branch_id_list().get(self).cloned()
397 }
398
399 pub fn target_spacing(&self) -> Duration {
404 let spacing_seconds = match self {
405 Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING,
406 Blossom | Heartwood | Canopy | Nu5 | Nu6 | Nu6_1 | Nu7 => {
407 POST_BLOSSOM_POW_TARGET_SPACING.into()
408 }
409
410 #[cfg(zcash_unstable = "zfuture")]
411 ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(),
412 };
413
414 Duration::seconds(spacing_seconds)
415 }
416
417 pub fn target_spacing_for_height(network: &Network, height: block::Height) -> Duration {
421 NetworkUpgrade::current(network, height).target_spacing()
422 }
423
424 pub fn target_spacings(
426 network: &Network,
427 ) -> impl Iterator<Item = (block::Height, Duration)> + '_ {
428 [
429 (NetworkUpgrade::Genesis, PRE_BLOSSOM_POW_TARGET_SPACING),
430 (
431 NetworkUpgrade::Blossom,
432 POST_BLOSSOM_POW_TARGET_SPACING.into(),
433 ),
434 ]
435 .into_iter()
436 .filter_map(move |(upgrade, spacing_seconds)| {
437 let activation_height = upgrade.activation_height(network)?;
438 let target_spacing = Duration::seconds(spacing_seconds);
439 Some((activation_height, target_spacing))
440 })
441 }
442
443 pub fn minimum_difficulty_spacing_for_height(
448 network: &Network,
449 height: block::Height,
450 ) -> Option<Duration> {
451 match (network, height) {
452 (Network::Testnet(_params), height)
454 if height < TESTNET_MINIMUM_DIFFICULTY_START_HEIGHT =>
455 {
456 None
457 }
458 (Network::Mainnet, _) => None,
459 (Network::Testnet(_params), _) => {
460 let network_upgrade = NetworkUpgrade::current(network, height);
461 Some(network_upgrade.target_spacing() * TESTNET_MINIMUM_DIFFICULTY_GAP_MULTIPLIER)
462 }
463 }
464 }
465
466 pub fn is_testnet_min_difficulty_block(
482 network: &Network,
483 block_height: block::Height,
484 block_time: DateTime<Utc>,
485 previous_block_time: DateTime<Utc>,
486 ) -> bool {
487 let block_time_gap = block_time - previous_block_time;
488 if let Some(min_difficulty_gap) =
489 NetworkUpgrade::minimum_difficulty_spacing_for_height(network, block_height)
490 {
491 block_time_gap > min_difficulty_gap
492 } else {
493 false
494 }
495 }
496
497 pub fn averaging_window_timespan(&self) -> Duration {
501 self.target_spacing() * POW_AVERAGING_WINDOW.try_into().expect("fits in i32")
502 }
503
504 pub fn averaging_window_timespan_for_height(
508 network: &Network,
509 height: block::Height,
510 ) -> Duration {
511 NetworkUpgrade::current(network, height).averaging_window_timespan()
512 }
513
514 pub fn iter() -> impl DoubleEndedIterator<Item = NetworkUpgrade> {
516 NETWORK_UPGRADES_IN_ORDER.iter().copied()
517 }
518}
519
520impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
521 fn from(nu: zcash_protocol::consensus::NetworkUpgrade) -> Self {
522 match nu {
523 zcash_protocol::consensus::NetworkUpgrade::Overwinter => Self::Overwinter,
524 zcash_protocol::consensus::NetworkUpgrade::Sapling => Self::Sapling,
525 zcash_protocol::consensus::NetworkUpgrade::Blossom => Self::Blossom,
526 zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
527 zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
528 zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
529 zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
530 zcash_protocol::consensus::NetworkUpgrade::Nu6_1 => Self::Nu6_1,
531 #[cfg(zcash_unstable = "nu7")]
532 zcash_protocol::consensus::NetworkUpgrade::Nu7 => Self::Nu7,
533 #[cfg(zcash_unstable = "zfuture")]
534 zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture,
535 }
536 }
537}
538
539impl ConsensusBranchId {
540 pub const RPC_MISSING_ID: ConsensusBranchId = ConsensusBranchId(0);
549
550 pub fn current(network: &Network, height: block::Height) -> Option<ConsensusBranchId> {
554 NetworkUpgrade::current(network, height).branch_id()
555 }
556}