1use std::str::FromStr;
5use std::sync::LazyLock;
6
7use indexmap::IndexMap;
8use libp2p::Multiaddr;
9use num_traits::Zero;
10use serde::{Deserialize, Serialize};
11use strum::{Display, EnumIter, IntoEnumIterator};
12use tracing::warn;
13
14use crate::beacon::{BeaconPoint, BeaconSchedule, DrandBeacon, DrandConfig};
15use crate::db::SettingsStore;
16use crate::eth::EthChainId;
17use crate::prelude::*;
18use crate::shim::{
19 clock::{ChainEpoch, EPOCH_DURATION_SECONDS, EPOCHS_IN_DAY},
20 econ::TokenAmount,
21 machine::BuiltinActorManifest,
22 runtime::Policy,
23 sector::{RegisteredPoStProofV3, RegisteredSealProofV3},
24 version::NetworkVersion,
25};
26use crate::utils::misc::env::env_or_default;
27use crate::{make_butterfly_policy, make_calibnet_policy, make_devnet_policy, make_mainnet_policy};
28
29pub use network_name::{GenesisNetworkName, StateNetworkName};
30
31mod actors_bundle;
32pub use actors_bundle::{
33 ACTOR_BUNDLES, ACTOR_BUNDLES_METADATA, ActorBundleInfo, ActorBundleMetadata,
34 generate_actor_bundle, get_actor_bundles_metadata,
35};
36
37mod drand;
38
39pub mod network_name;
40
41pub mod butterflynet;
42pub mod calibnet;
43pub mod devnet;
44pub mod mainnet;
45
46pub mod metrics;
47
48pub const NEWEST_NETWORK_VERSION: NetworkVersion = NetworkVersion::V29;
50
51pub const UPGRADE_HEIGHT_UNSCHEDULED: ChainEpoch = 999_999_999_999_999;
53
54const ENV_FOREST_BLOCK_DELAY_SECS: &str = "FOREST_BLOCK_DELAY_SECS";
55const ENV_FOREST_PROPAGATION_DELAY_SECS: &str = "FOREST_PROPAGATION_DELAY_SECS";
56const ENV_PLEDGE_RULE_RAMP: &str = "FOREST_PLEDGE_RULE_RAMP";
57
58static INITIAL_FIL_RESERVED: LazyLock<TokenAmount> =
59 LazyLock::new(|| TokenAmount::from_whole(300_000_000));
60
61#[derive(
64 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash, derive_more::Display,
65)]
66#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
67#[serde(tag = "type", content = "name", rename_all = "lowercase")]
68#[display(rename_all = "lowercase")]
69pub enum NetworkChain {
70 #[default]
71 Mainnet,
72 Calibnet,
73 Butterflynet,
74 Devnet(String),
75}
76
77impl FromStr for NetworkChain {
78 type Err = anyhow::Error;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 match s {
82 mainnet::NETWORK_COMMON_NAME | mainnet::NETWORK_GENESIS_NAME => {
83 Ok(NetworkChain::Mainnet)
84 }
85 calibnet::NETWORK_COMMON_NAME | calibnet::NETWORK_GENESIS_NAME => {
86 Ok(NetworkChain::Calibnet)
87 }
88 butterflynet::NETWORK_COMMON_NAME => Ok(NetworkChain::Butterflynet),
89 name => Ok(NetworkChain::Devnet(name.to_owned())),
90 }
91 }
92}
93
94impl NetworkChain {
95 pub fn genesis_name(&self) -> GenesisNetworkName {
101 match self {
102 NetworkChain::Mainnet => mainnet::NETWORK_GENESIS_NAME.into(),
103 NetworkChain::Calibnet => calibnet::NETWORK_GENESIS_NAME.into(),
104 _ => self.to_string().into(),
105 }
106 }
107 pub fn from_genesis(cid: &Cid) -> Option<Self> {
110 if cid == &*mainnet::GENESIS_CID {
111 Some(Self::Mainnet)
112 } else if cid == &*calibnet::GENESIS_CID {
113 Some(Self::Calibnet)
114 } else if cid == &*butterflynet::GENESIS_CID {
115 Some(Self::Butterflynet)
116 } else {
117 None
118 }
119 }
120
121 pub fn from_genesis_or_devnet_placeholder(cid: &Cid) -> Self {
126 Self::from_genesis(cid).unwrap_or_else(|| Self::Devnet(String::from("devnet")))
127 }
128
129 pub fn is_testnet(&self) -> bool {
130 !matches!(self, NetworkChain::Mainnet)
131 }
132
133 pub fn is_devnet(&self) -> bool {
134 matches!(self, NetworkChain::Devnet(..))
135 }
136}
137
138#[derive(
140 Debug, Default, Display, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, EnumIter,
141)]
142#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
143pub enum Height {
144 #[default]
145 Breeze,
146 Smoke,
147 Ignition,
148 Refuel,
149 Assembly,
150 Tape,
151 Liftoff,
152 Kumquat,
153 Calico,
154 Persian,
155 Orange,
156 Claus,
157 Trust,
158 Norwegian,
159 Turbo,
160 Hyperdrive,
161 Chocolate,
162 OhSnap,
163 Skyr,
164 Shark,
165 Hygge,
166 Lightning,
167 Thunder,
168 Watermelon,
169 WatermelonFix,
170 WatermelonFix2,
171 Dragon,
172 DragonFix,
173 Phoenix,
174 Waffle,
175 TukTuk,
176 Teep,
177 Tock,
178 TockFix,
179 GoldenWeek,
180 FireHorse,
181 Solstice,
182}
183
184impl From<Height> for NetworkVersion {
185 fn from(height: Height) -> NetworkVersion {
186 match height {
187 Height::Breeze => NetworkVersion::V1,
188 Height::Smoke => NetworkVersion::V2,
189 Height::Ignition => NetworkVersion::V3,
190 Height::Refuel => NetworkVersion::V3,
191 Height::Assembly => NetworkVersion::V4,
192 Height::Tape => NetworkVersion::V5,
193 Height::Liftoff => NetworkVersion::V5,
194 Height::Kumquat => NetworkVersion::V6,
195 Height::Calico => NetworkVersion::V7,
196 Height::Persian => NetworkVersion::V8,
197 Height::Orange => NetworkVersion::V9,
198 Height::Claus => NetworkVersion::V9,
199 Height::Trust => NetworkVersion::V10,
200 Height::Norwegian => NetworkVersion::V11,
201 Height::Turbo => NetworkVersion::V12,
202 Height::Hyperdrive => NetworkVersion::V13,
203 Height::Chocolate => NetworkVersion::V14,
204 Height::OhSnap => NetworkVersion::V15,
205 Height::Skyr => NetworkVersion::V16,
206 Height::Shark => NetworkVersion::V17,
207 Height::Hygge => NetworkVersion::V18,
208 Height::Lightning => NetworkVersion::V19,
209 Height::Thunder => NetworkVersion::V20,
210 Height::Watermelon => NetworkVersion::V21,
211 Height::WatermelonFix => NetworkVersion::V21,
212 Height::WatermelonFix2 => NetworkVersion::V21,
213 Height::Dragon => NetworkVersion::V22,
214 Height::DragonFix => NetworkVersion::V22,
215 Height::Phoenix => NetworkVersion::V22,
216 Height::Waffle => NetworkVersion::V23,
217 Height::TukTuk => NetworkVersion::V24,
218 Height::Teep => NetworkVersion::V25,
219 Height::Tock => NetworkVersion::V26,
220 Height::TockFix => NetworkVersion::V26,
221 Height::GoldenWeek => NetworkVersion::V27,
222 Height::FireHorse => NetworkVersion::V28,
223 Height::Solstice => NetworkVersion::V29,
224 }
225 }
226}
227
228#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
229#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
230pub struct HeightInfo {
231 pub epoch: ChainEpoch,
232 pub bundle: Option<Cid>,
233}
234
235pub struct HeightInfoWithActorManifest<'a> {
236 #[allow(dead_code)]
237 pub height: Height,
238 pub info: &'a HeightInfo,
239 pub manifest_cid: Cid,
240}
241
242impl<'a> HeightInfoWithActorManifest<'a> {
243 pub fn manifest(&self, store: &impl Blockstore) -> anyhow::Result<BuiltinActorManifest> {
244 BuiltinActorManifest::load_manifest(store, &self.manifest_cid)
245 }
246}
247
248#[derive(Clone)]
249struct DrandPoint<'a> {
250 pub height: ChainEpoch,
251 pub config: &'a LazyLock<DrandConfig<'a>>,
252}
253
254#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
256#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
257#[serde(default)]
258pub struct ChainConfig {
259 pub network: NetworkChain,
260 pub genesis_cid: Option<String>,
261 #[cfg_attr(test, arbitrary(gen(
262 |g: &mut quickcheck::Gen| {
263 let addr = std::net::Ipv4Addr::arbitrary(&mut *g);
264 let n = u8::arbitrary(g) as usize;
265 vec![addr.into(); n]
266 }
267 )))]
268 pub bootstrap_peers: Vec<Multiaddr>,
269 pub block_delay_secs: u32,
270 pub propagation_delay_secs: u32,
271 pub genesis_network: NetworkVersion,
272 #[cfg_attr(test, arbitrary(gen(|_g| devnet::HEIGHT_INFOS.clone())))]
273 pub height_infos: IndexMap<Height, HeightInfo>,
274 #[cfg_attr(test, arbitrary(gen(|_g| Policy::default())))]
275 pub policy: Policy,
276 pub eth_chain_id: EthChainId,
277 pub breeze_gas_tamping_duration: i64,
278 pub fip0081_ramp_duration_epochs: u64,
280 pub upgrade_teep_initial_fil_reserved: Option<TokenAmount>,
282 pub f3_enabled: bool,
283 pub f3_consensus: bool,
285 pub f3_bootstrap_epoch: ChainEpoch,
286 pub f3_initial_power_table: Option<Cid>,
287 pub enable_indexer: bool,
288 pub default_max_fee: TokenAmount,
289}
290
291impl ChainConfig {
292 pub fn mainnet() -> Self {
293 use mainnet::*;
294 Self {
295 network: NetworkChain::Mainnet,
296 genesis_cid: Some(GENESIS_CID.to_string()),
297 bootstrap_peers: DEFAULT_BOOTSTRAP.clone(),
298 block_delay_secs: env_or_default(
299 ENV_FOREST_BLOCK_DELAY_SECS,
300 EPOCH_DURATION_SECONDS as u32,
301 ),
302 propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 10),
303 genesis_network: GENESIS_NETWORK_VERSION,
304 height_infos: HEIGHT_INFOS.clone(),
305 policy: make_mainnet_policy!(v13).into(),
306 eth_chain_id: ETH_CHAIN_ID,
307 breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
308 fip0081_ramp_duration_epochs: 365 * EPOCHS_IN_DAY as u64,
310 upgrade_teep_initial_fil_reserved: None,
311 f3_enabled: true,
312 f3_consensus: true,
313 f3_bootstrap_epoch: 4920480,
315 f3_initial_power_table: Some(
316 "bafy2bzacecklgxd2eksmodvhgurqvorkg3wamgqkrunir3al2gchv2cikgmbu"
317 .parse()
318 .expect("invalid f3_initial_power_table"),
319 ),
320 enable_indexer: false,
321 default_max_fee: TokenAmount::zero(),
322 }
323 }
324
325 pub fn calibnet() -> Self {
326 use calibnet::*;
327 Self {
328 network: NetworkChain::Calibnet,
329 genesis_cid: Some(GENESIS_CID.to_string()),
330 bootstrap_peers: DEFAULT_BOOTSTRAP.clone(),
331 block_delay_secs: env_or_default(
332 ENV_FOREST_BLOCK_DELAY_SECS,
333 EPOCH_DURATION_SECONDS as u32,
334 ),
335 propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 10),
336 genesis_network: GENESIS_NETWORK_VERSION,
337 height_infos: HEIGHT_INFOS
338 .clone()
339 .into_iter()
340 .sorted_by_key(|(_, v)| v.epoch)
341 .collect(),
342 policy: make_calibnet_policy!(v13).into(),
343 eth_chain_id: ETH_CHAIN_ID,
344 breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
345 fip0081_ramp_duration_epochs: 3 * EPOCHS_IN_DAY as u64,
347 upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_200_000_000)),
349 f3_enabled: true,
352 f3_consensus: true,
353 f3_bootstrap_epoch: 3_451_774,
355 f3_initial_power_table: Some(
356 "bafy2bzacednijkh5dhb6jb7snxhhtjt7zuqaydlewoha3ordhy76dhgwtmptg"
357 .parse()
358 .expect("invalid f3_initial_power_table"),
359 ),
360 enable_indexer: false,
361 default_max_fee: TokenAmount::zero(),
362 }
363 }
364
365 pub fn devnet() -> Self {
366 use devnet::*;
367 Self {
368 network: NetworkChain::Devnet("devnet".to_string()),
369 genesis_cid: None,
370 bootstrap_peers: Vec::new(),
371 block_delay_secs: env_or_default(ENV_FOREST_BLOCK_DELAY_SECS, 4),
372 propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 1),
373 genesis_network: *GENESIS_NETWORK_VERSION,
374 height_infos: HEIGHT_INFOS
375 .clone()
376 .into_iter()
377 .sorted_by_key(|(_, v)| v.epoch)
378 .collect(),
379 policy: make_devnet_policy!(v13).into(),
380 eth_chain_id: ETH_CHAIN_ID,
381 breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
382 fip0081_ramp_duration_epochs: env_or_default(ENV_PLEDGE_RULE_RAMP, 200),
384 upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_400_000_000)),
386 f3_enabled: false,
387 f3_consensus: false,
388 f3_bootstrap_epoch: -1,
389 f3_initial_power_table: None,
390 enable_indexer: false,
391 default_max_fee: TokenAmount::zero(),
392 }
393 }
394
395 pub fn butterflynet() -> Self {
396 use butterflynet::*;
397 Self {
398 network: NetworkChain::Butterflynet,
399 genesis_cid: Some(GENESIS_CID.to_string()),
400 bootstrap_peers: DEFAULT_BOOTSTRAP.clone(),
401 block_delay_secs: env_or_default(
402 ENV_FOREST_BLOCK_DELAY_SECS,
403 EPOCH_DURATION_SECONDS as u32,
404 ),
405 propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 6),
406 genesis_network: GENESIS_NETWORK_VERSION,
407 height_infos: HEIGHT_INFOS
408 .clone()
409 .into_iter()
410 .sorted_by_key(|(_, v)| v.epoch)
411 .collect(),
412 policy: make_butterfly_policy!(v13).into(),
413 eth_chain_id: ETH_CHAIN_ID,
414 breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
415 fip0081_ramp_duration_epochs: env_or_default(
417 ENV_PLEDGE_RULE_RAMP,
418 365 * EPOCHS_IN_DAY as u64,
419 ),
420 upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_600_000_000)),
422 f3_enabled: true,
423 f3_consensus: true,
424 f3_bootstrap_epoch: 1000,
425 f3_initial_power_table: None,
426 enable_indexer: false,
427 default_max_fee: TokenAmount::zero(),
428 }
429 }
430
431 pub fn from_chain(network_chain: &NetworkChain) -> Self {
432 match network_chain {
433 NetworkChain::Mainnet => Self::mainnet(),
434 NetworkChain::Calibnet => Self::calibnet(),
435 NetworkChain::Butterflynet => Self::butterflynet(),
436 NetworkChain::Devnet(name) => Self {
437 network: NetworkChain::Devnet(name.clone()),
438 ..Self::devnet()
439 },
440 }
441 }
442
443 fn network_height(&self, epoch: ChainEpoch) -> Option<Height> {
444 self.height_infos
445 .iter()
446 .rev()
447 .find(|(_, info)| epoch > info.epoch)
448 .map(|(height, _)| *height)
449 }
450
451 pub fn network_height_with_actor_bundle<'a>(
453 &'a self,
454 epoch: ChainEpoch,
455 ) -> Option<HeightInfoWithActorManifest<'a>> {
456 if let Some((height, info, manifest_cid)) = self
457 .height_infos
458 .iter()
459 .rev()
460 .filter_map(|(height, info)| info.bundle.map(|bundle| (*height, info, bundle)))
461 .find(|(_, info, _)| epoch > info.epoch)
462 {
463 Some(HeightInfoWithActorManifest {
464 height,
465 info,
466 manifest_cid,
467 })
468 } else {
469 None
470 }
471 }
472
473 pub fn network_version(&self, epoch: ChainEpoch) -> NetworkVersion {
476 self.network_height(epoch)
477 .map(NetworkVersion::from)
478 .unwrap_or_else(|| self.genesis_network_version())
479 .max(self.genesis_network)
480 }
481
482 pub fn network_version_revision(&self, epoch: ChainEpoch) -> i64 {
485 if let Some(height) = self.network_height(epoch) {
486 let nv = NetworkVersion::from(height);
487 if let Some(rev0_height) = Height::iter().find(|h| NetworkVersion::from(*h) == nv) {
488 return (height as i64) - (rev0_height as i64);
489 }
490 }
491 0
492 }
493
494 pub fn get_beacon_schedule(&self, genesis_ts: u64) -> BeaconSchedule {
495 let ds_iter = match self.network {
496 NetworkChain::Mainnet => mainnet::DRAND_SCHEDULE.iter(),
497 NetworkChain::Calibnet => calibnet::DRAND_SCHEDULE.iter(),
498 NetworkChain::Butterflynet => butterflynet::DRAND_SCHEDULE.iter(),
499 NetworkChain::Devnet(_) => devnet::DRAND_SCHEDULE.iter(),
500 };
501
502 BeaconSchedule(
503 ds_iter
504 .map(|dc| {
505 BeaconPoint::new(
506 dc.height,
507 DrandBeacon::new(genesis_ts, u64::from(self.block_delay_secs), dc.config),
508 )
509 })
510 .collect(),
511 )
512 }
513
514 pub fn epoch(&self, height: Height) -> ChainEpoch {
515 self.height_infos
516 .iter()
517 .rev()
518 .find_map(|(infos_height, info)| {
519 if *infos_height == height {
520 Some(info.epoch)
521 } else {
522 None
523 }
524 })
525 .unwrap_or(0)
526 }
527
528 pub fn expensive_fork_between(
530 &self,
531 parent: ChainEpoch,
532 height: ChainEpoch,
533 ) -> Option<ChainEpoch> {
534 if parent >= height {
535 return None;
536 }
537 crate::state_migration::get_migrations::<crate::db::DbImpl>(&self.network)
538 .iter()
539 .filter_map(|(h, _)| self.height_infos.get(h).map(|info| info.epoch))
540 .filter(|epoch| *epoch >= parent && *epoch < height)
541 .min()
542 }
543
544 pub fn has_expensive_fork_between(&self, parent: ChainEpoch, height: ChainEpoch) -> bool {
547 self.expensive_fork_between(parent, height).is_some()
548 }
549
550 pub async fn genesis_bytes<DB: SettingsStore>(
551 &self,
552 db: &DB,
553 ) -> anyhow::Result<Option<Vec<u8>>> {
554 Ok(match self.network {
555 NetworkChain::Mainnet => Some(mainnet::DEFAULT_GENESIS.to_vec()),
556 NetworkChain::Calibnet => Some(calibnet::DEFAULT_GENESIS.to_vec()),
557 NetworkChain::Butterflynet => Some(butterflynet::fetch_genesis(db).await?),
559 NetworkChain::Devnet(_) => None,
560 })
561 }
562
563 pub fn is_testnet(&self) -> bool {
564 self.network.is_testnet()
565 }
566
567 pub fn is_devnet(&self) -> bool {
568 self.network.is_devnet()
569 }
570
571 pub fn genesis_network_version(&self) -> NetworkVersion {
572 self.genesis_network
573 }
574
575 pub fn initial_fil_reserved(&self, network_version: NetworkVersion) -> &TokenAmount {
576 match &self.upgrade_teep_initial_fil_reserved {
577 Some(fil) if network_version >= NetworkVersion::V25 => fil,
578 _ => &INITIAL_FIL_RESERVED,
579 }
580 }
581
582 pub fn initial_fil_reserved_at_height(&self, height: ChainEpoch) -> &TokenAmount {
583 let network_version = self.network_version(height);
584 self.initial_fil_reserved(network_version)
585 }
586}
587
588impl Default for ChainConfig {
589 fn default() -> Self {
590 ChainConfig::mainnet()
591 }
592}
593
594pub(crate) fn parse_bootstrap_peers(bootstrap_peer_list: &str) -> Vec<Multiaddr> {
595 bootstrap_peer_list
596 .split('\n')
597 .filter(|s| !s.is_empty())
598 .map(|s| {
599 Multiaddr::from_str(s).unwrap_or_else(|e| panic!("invalid bootstrap peer {s}: {e}"))
600 })
601 .collect()
602}
603
604#[allow(dead_code)]
605fn get_upgrade_epoch_by_height<'a>(
606 mut height_infos: impl Iterator<Item = &'a (Height, HeightInfo)>,
607 height: Height,
608) -> Option<ChainEpoch> {
609 height_infos.find_map(|(infos_height, info)| {
610 if *infos_height == height {
611 Some(info.epoch)
612 } else {
613 None
614 }
615 })
616}
617
618fn get_upgrade_height_from_env(env_var_key: &str) -> Option<ChainEpoch> {
619 if let Ok(value) = std::env::var(env_var_key) {
620 if let Ok(epoch) = value.parse() {
621 return Some(epoch);
622 } else {
623 warn!("Failed to parse {env_var_key}={value}, value should be an integer");
624 }
625 }
626 None
627}
628
629#[macro_export]
630macro_rules! make_height {
631 ($id:ident,$epoch:expr) => {
632 (
633 Height::$id,
634 HeightInfo {
635 epoch: $epoch,
636 bundle: None,
637 },
638 )
639 };
640 ($id:ident,$epoch:expr,$bundle:expr) => {
641 (
642 Height::$id,
643 HeightInfo {
644 epoch: $epoch,
645 bundle: Some(Cid::try_from($bundle).unwrap()),
646 },
647 )
648 };
649}
650
651pub fn calculate_expected_epoch(
658 now_timestamp: u64,
659 genesis_timestamp: u64,
660 block_delay_secs: u32,
661) -> i64 {
662 (now_timestamp.saturating_sub(genesis_timestamp) / u64::from(block_delay_secs)) as i64
663}
664
665#[cfg(test)]
666mod tests {
667 use super::*;
668 use rstest::rstest;
669
670 fn heights_are_present(height_infos: &IndexMap<Height, HeightInfo>) {
671 const REQUIRED_HEIGHTS: [Height; 32] = [
674 Height::Breeze,
675 Height::Smoke,
676 Height::Ignition,
677 Height::Refuel,
678 Height::Assembly,
679 Height::Tape,
680 Height::Liftoff,
681 Height::Kumquat,
682 Height::Calico,
683 Height::Persian,
684 Height::Orange,
685 Height::Claus,
686 Height::Trust,
687 Height::Norwegian,
688 Height::Turbo,
689 Height::Hyperdrive,
690 Height::Chocolate,
691 Height::OhSnap,
692 Height::Skyr,
693 Height::Shark,
694 Height::Hygge,
695 Height::Lightning,
696 Height::Thunder,
697 Height::Watermelon,
698 Height::Dragon,
699 Height::Phoenix,
700 Height::Waffle,
701 Height::TukTuk,
702 Height::Teep,
703 Height::GoldenWeek,
704 Height::FireHorse,
705 Height::Solstice,
706 ];
707
708 for height in &REQUIRED_HEIGHTS {
709 assert!(height_infos.get(height).is_some());
710 }
711 }
712
713 #[test]
714 fn test_mainnet_heights() {
715 heights_are_present(&mainnet::HEIGHT_INFOS);
716 }
717
718 #[test]
719 fn has_expensive_fork_between_matches_upgrade_epochs() {
720 let cfg = ChainConfig::mainnet();
721 let shark = cfg.epoch(Height::Shark);
722 assert!(cfg.has_expensive_fork_between(shark - 1, shark + 1));
723 assert!(!cfg.has_expensive_fork_between(shark - 1, shark));
724 }
725
726 #[test]
727 fn expensive_fork_between_returns_lowest_fork_in_window() {
728 let cfg = ChainConfig::calibnet();
729 let shark = cfg.epoch(Height::Shark);
730 let hygge = cfg.epoch(Height::Hygge);
731 assert_eq!(cfg.expensive_fork_between(shark - 1, shark), None);
732 assert_eq!(cfg.expensive_fork_between(shark, shark + 1), Some(shark));
733 assert_eq!(
734 cfg.expensive_fork_between(shark - 1, hygge + 1),
735 Some(shark)
736 );
737 assert_eq!(cfg.expensive_fork_between(shark + 1, shark + 1), None);
738 }
739
740 #[test]
741 fn test_calibnet_heights() {
742 heights_are_present(&calibnet::HEIGHT_INFOS);
743 }
744
745 #[test]
746 fn test_devnet_heights() {
747 heights_are_present(&devnet::HEIGHT_INFOS);
748 }
749
750 #[test]
751 fn test_butterflynet_heights() {
752 heights_are_present(&butterflynet::HEIGHT_INFOS);
753 }
754
755 #[test]
756 fn test_get_upgrade_height_no_env_var() {
757 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_1");
758 assert_eq!(epoch, None);
759 }
760
761 #[test]
762 fn test_get_upgrade_height_valid_env_var() {
763 unsafe { std::env::set_var("FOREST_TEST_VAR_2", "10") };
764 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_2");
765 assert_eq!(epoch, Some(10));
766 }
767
768 #[test]
769 fn test_get_upgrade_height_invalid_env_var() {
770 unsafe { std::env::set_var("FOREST_TEST_VAR_3", "foo") };
771 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_3");
772 assert_eq!(epoch, None);
773 }
774
775 #[test]
776 fn test_calculate_expected_epoch() {
777 assert_eq!(0, calculate_expected_epoch(0, 0, 1));
779 assert_eq!(5, calculate_expected_epoch(5, 0, 1));
780
781 let mainnet_genesis = 1598306400;
782 let mainnet_block_delay = 30;
783
784 assert_eq!(
785 0,
786 calculate_expected_epoch(mainnet_genesis, mainnet_genesis, mainnet_block_delay)
787 );
788
789 assert_eq!(
790 0,
791 calculate_expected_epoch(
792 mainnet_genesis + u64::from(mainnet_block_delay) - 1,
793 mainnet_genesis,
794 mainnet_block_delay
795 )
796 );
797
798 assert_eq!(
799 1,
800 calculate_expected_epoch(
801 mainnet_genesis + u64::from(mainnet_block_delay),
802 mainnet_genesis,
803 mainnet_block_delay
804 )
805 );
806 }
807
808 #[test]
809 fn network_chain_display() {
810 assert_eq!(
811 NetworkChain::Mainnet.to_string(),
812 mainnet::NETWORK_COMMON_NAME
813 );
814 assert_eq!(
815 NetworkChain::Calibnet.to_string(),
816 calibnet::NETWORK_COMMON_NAME
817 );
818 assert_eq!(
819 NetworkChain::Butterflynet.to_string(),
820 butterflynet::NETWORK_COMMON_NAME
821 );
822 assert_eq!(
823 NetworkChain::Devnet("dummydevnet".into()).to_string(),
824 "dummydevnet"
825 );
826 }
827
828 #[test]
829 fn chain_config() {
830 ChainConfig::mainnet();
831 ChainConfig::calibnet();
832 ChainConfig::devnet();
833 ChainConfig::butterflynet();
834 }
835
836 #[test]
837 fn network_version() {
838 let cfg = ChainConfig::calibnet();
839 assert_eq!(cfg.network_version(1_013_134 - 1), NetworkVersion::V20);
840 assert_eq!(cfg.network_version(1_013_134), NetworkVersion::V20);
841 assert_eq!(cfg.network_version(1_013_134 + 1), NetworkVersion::V21);
842 assert_eq!(cfg.network_version_revision(1_013_134 + 1), 0);
843 assert_eq!(cfg.network_version(1_070_494), NetworkVersion::V21);
844 assert_eq!(cfg.network_version_revision(1_070_494), 0);
845 assert_eq!(cfg.network_version(1_070_494 + 1), NetworkVersion::V21);
846 assert_eq!(cfg.network_version_revision(1_070_494 + 1), 1);
847 }
848
849 #[test]
850 fn test_network_height_with_actor_bundle() {
851 let cfg = ChainConfig::mainnet();
852 let info = cfg.network_height_with_actor_bundle(5_348_280 + 1).unwrap();
853 assert_eq!(info.height, Height::GoldenWeek);
854 let info = cfg.network_height_with_actor_bundle(5_348_280).unwrap();
855 assert_eq!(info.height, Height::Teep);
857 let info = cfg.network_height_with_actor_bundle(5_348_280 - 1).unwrap();
858 assert_eq!(info.height, Height::Teep);
859 assert!(cfg.network_height_with_actor_bundle(1).is_none());
860 assert!(cfg.network_height_with_actor_bundle(0).is_none());
861 }
862
863 #[rstest]
864 #[case(ChainConfig::mainnet())]
865 #[case(ChainConfig::calibnet())]
866 #[case(ChainConfig::butterflynet())]
867 #[case(ChainConfig::devnet())]
868 fn ensure_height_info_sorted(#[case] cfg: ChainConfig) {
869 assert!(
870 cfg.height_infos.is_sorted_by_key(|_, v| v.epoch),
871 "height_infos should be sorted in {} config",
872 cfg.network
873 )
874 }
875}