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