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 async fn genesis_bytes<DB: SettingsStore>(
516 &self,
517 db: &DB,
518 ) -> anyhow::Result<Option<Vec<u8>>> {
519 Ok(match self.network {
520 NetworkChain::Mainnet => Some(mainnet::DEFAULT_GENESIS.to_vec()),
521 NetworkChain::Calibnet => Some(calibnet::DEFAULT_GENESIS.to_vec()),
522 NetworkChain::Butterflynet => Some(butterflynet::fetch_genesis(db).await?),
524 NetworkChain::Devnet(_) => None,
525 })
526 }
527
528 pub fn is_testnet(&self) -> bool {
529 self.network.is_testnet()
530 }
531
532 pub fn is_devnet(&self) -> bool {
533 self.network.is_devnet()
534 }
535
536 pub fn genesis_network_version(&self) -> NetworkVersion {
537 self.genesis_network
538 }
539
540 pub fn initial_fil_reserved(&self, network_version: NetworkVersion) -> &TokenAmount {
541 match &self.upgrade_teep_initial_fil_reserved {
542 Some(fil) if network_version >= NetworkVersion::V25 => fil,
543 _ => &INITIAL_FIL_RESERVED,
544 }
545 }
546
547 pub fn initial_fil_reserved_at_height(&self, height: i64) -> &TokenAmount {
548 let network_version = self.network_version(height);
549 self.initial_fil_reserved(network_version)
550 }
551}
552
553impl Default for ChainConfig {
554 fn default() -> Self {
555 ChainConfig::mainnet()
556 }
557}
558
559pub(crate) fn parse_bootstrap_peers(bootstrap_peer_list: &str) -> Vec<Multiaddr> {
560 bootstrap_peer_list
561 .split('\n')
562 .filter(|s| !s.is_empty())
563 .map(|s| {
564 Multiaddr::from_str(s).unwrap_or_else(|e| panic!("invalid bootstrap peer {s}: {e}"))
565 })
566 .collect()
567}
568
569#[allow(dead_code)]
570fn get_upgrade_epoch_by_height<'a>(
571 mut height_infos: impl Iterator<Item = &'a (Height, HeightInfo)>,
572 height: Height,
573) -> Option<ChainEpoch> {
574 height_infos.find_map(|(infos_height, info)| {
575 if *infos_height == height {
576 Some(info.epoch)
577 } else {
578 None
579 }
580 })
581}
582
583fn get_upgrade_height_from_env(env_var_key: &str) -> Option<ChainEpoch> {
584 if let Ok(value) = std::env::var(env_var_key) {
585 if let Ok(epoch) = value.parse() {
586 return Some(epoch);
587 } else {
588 warn!("Failed to parse {env_var_key}={value}, value should be an integer");
589 }
590 }
591 None
592}
593
594#[macro_export]
595macro_rules! make_height {
596 ($id:ident,$epoch:expr) => {
597 (
598 Height::$id,
599 HeightInfo {
600 epoch: $epoch,
601 bundle: None,
602 },
603 )
604 };
605 ($id:ident,$epoch:expr,$bundle:expr) => {
606 (
607 Height::$id,
608 HeightInfo {
609 epoch: $epoch,
610 bundle: Some(Cid::try_from($bundle).unwrap()),
611 },
612 )
613 };
614}
615
616pub fn calculate_expected_epoch(
623 now_timestamp: u64,
624 genesis_timestamp: u64,
625 block_delay_secs: u32,
626) -> i64 {
627 (now_timestamp.saturating_sub(genesis_timestamp) / u64::from(block_delay_secs)) as i64
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633
634 fn heights_are_present(height_infos: &HashMap<Height, HeightInfo>) {
635 const REQUIRED_HEIGHTS: [Height; 31] = [
638 Height::Breeze,
639 Height::Smoke,
640 Height::Ignition,
641 Height::Refuel,
642 Height::Assembly,
643 Height::Tape,
644 Height::Liftoff,
645 Height::Kumquat,
646 Height::Calico,
647 Height::Persian,
648 Height::Orange,
649 Height::Claus,
650 Height::Trust,
651 Height::Norwegian,
652 Height::Turbo,
653 Height::Hyperdrive,
654 Height::Chocolate,
655 Height::OhSnap,
656 Height::Skyr,
657 Height::Shark,
658 Height::Hygge,
659 Height::Lightning,
660 Height::Thunder,
661 Height::Watermelon,
662 Height::Dragon,
663 Height::Phoenix,
664 Height::Waffle,
665 Height::TukTuk,
666 Height::Teep,
667 Height::GoldenWeek,
668 Height::FireHorse,
669 ];
670
671 for height in &REQUIRED_HEIGHTS {
672 assert!(height_infos.get(height).is_some());
673 }
674 }
675
676 #[test]
677 fn test_mainnet_heights() {
678 heights_are_present(&mainnet::HEIGHT_INFOS);
679 }
680
681 #[test]
682 fn test_calibnet_heights() {
683 heights_are_present(&calibnet::HEIGHT_INFOS);
684 }
685
686 #[test]
687 fn test_devnet_heights() {
688 heights_are_present(&devnet::HEIGHT_INFOS);
689 }
690
691 #[test]
692 fn test_butterflynet_heights() {
693 heights_are_present(&butterflynet::HEIGHT_INFOS);
694 }
695
696 #[test]
697 fn test_get_upgrade_height_no_env_var() {
698 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_1");
699 assert_eq!(epoch, None);
700 }
701
702 #[test]
703 fn test_get_upgrade_height_valid_env_var() {
704 unsafe { std::env::set_var("FOREST_TEST_VAR_2", "10") };
705 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_2");
706 assert_eq!(epoch, Some(10));
707 }
708
709 #[test]
710 fn test_get_upgrade_height_invalid_env_var() {
711 unsafe { std::env::set_var("FOREST_TEST_VAR_3", "foo") };
712 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_3");
713 assert_eq!(epoch, None);
714 }
715
716 #[test]
717 fn test_calculate_expected_epoch() {
718 assert_eq!(0, calculate_expected_epoch(0, 0, 1));
720 assert_eq!(5, calculate_expected_epoch(5, 0, 1));
721
722 let mainnet_genesis = 1598306400;
723 let mainnet_block_delay = 30;
724
725 assert_eq!(
726 0,
727 calculate_expected_epoch(mainnet_genesis, mainnet_genesis, mainnet_block_delay)
728 );
729
730 assert_eq!(
731 0,
732 calculate_expected_epoch(
733 mainnet_genesis + u64::from(mainnet_block_delay) - 1,
734 mainnet_genesis,
735 mainnet_block_delay
736 )
737 );
738
739 assert_eq!(
740 1,
741 calculate_expected_epoch(
742 mainnet_genesis + u64::from(mainnet_block_delay),
743 mainnet_genesis,
744 mainnet_block_delay
745 )
746 );
747 }
748
749 #[test]
750 fn network_chain_display() {
751 assert_eq!(
752 NetworkChain::Mainnet.to_string(),
753 mainnet::NETWORK_COMMON_NAME
754 );
755 assert_eq!(
756 NetworkChain::Calibnet.to_string(),
757 calibnet::NETWORK_COMMON_NAME
758 );
759 assert_eq!(
760 NetworkChain::Butterflynet.to_string(),
761 butterflynet::NETWORK_COMMON_NAME
762 );
763 assert_eq!(
764 NetworkChain::Devnet("dummydevnet".into()).to_string(),
765 "dummydevnet"
766 );
767 }
768
769 #[test]
770 fn chain_config() {
771 ChainConfig::mainnet();
772 ChainConfig::calibnet();
773 ChainConfig::devnet();
774 ChainConfig::butterflynet();
775 }
776
777 #[test]
778 fn network_version() {
779 let cfg = ChainConfig::calibnet();
780 assert_eq!(cfg.network_version(1_013_134 - 1), NetworkVersion::V20);
781 assert_eq!(cfg.network_version(1_013_134), NetworkVersion::V20);
782 assert_eq!(cfg.network_version(1_013_134 + 1), NetworkVersion::V21);
783 assert_eq!(cfg.network_version_revision(1_013_134 + 1), 0);
784 assert_eq!(cfg.network_version(1_070_494), NetworkVersion::V21);
785 assert_eq!(cfg.network_version_revision(1_070_494), 0);
786 assert_eq!(cfg.network_version(1_070_494 + 1), NetworkVersion::V21);
787 assert_eq!(cfg.network_version_revision(1_070_494 + 1), 1);
788 }
789
790 #[test]
791 fn test_network_height_with_actor_bundle() {
792 let cfg = ChainConfig::mainnet();
793 let info = cfg.network_height_with_actor_bundle(5_348_280 + 1).unwrap();
794 assert_eq!(info.height, Height::GoldenWeek);
795 let info = cfg.network_height_with_actor_bundle(5_348_280).unwrap();
796 assert_eq!(info.height, Height::Teep);
798 let info = cfg.network_height_with_actor_bundle(5_348_280 - 1).unwrap();
799 assert_eq!(info.height, Height::Teep);
800 assert!(cfg.network_height_with_actor_bundle(1).is_none());
801 assert!(cfg.network_height_with_actor_bundle(0).is_none());
802 }
803}