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::V25;
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}
180
181impl From<Height> for NetworkVersion {
182 fn from(height: Height) -> NetworkVersion {
183 match height {
184 Height::Breeze => NetworkVersion::V1,
185 Height::Smoke => NetworkVersion::V2,
186 Height::Ignition => NetworkVersion::V3,
187 Height::Refuel => NetworkVersion::V3,
188 Height::Assembly => NetworkVersion::V4,
189 Height::Tape => NetworkVersion::V5,
190 Height::Liftoff => NetworkVersion::V5,
191 Height::Kumquat => NetworkVersion::V6,
192 Height::Calico => NetworkVersion::V7,
193 Height::Persian => NetworkVersion::V8,
194 Height::Orange => NetworkVersion::V9,
195 Height::Claus => NetworkVersion::V9,
196 Height::Trust => NetworkVersion::V10,
197 Height::Norwegian => NetworkVersion::V11,
198 Height::Turbo => NetworkVersion::V12,
199 Height::Hyperdrive => NetworkVersion::V13,
200 Height::Chocolate => NetworkVersion::V14,
201 Height::OhSnap => NetworkVersion::V15,
202 Height::Skyr => NetworkVersion::V16,
203 Height::Shark => NetworkVersion::V17,
204 Height::Hygge => NetworkVersion::V18,
205 Height::Lightning => NetworkVersion::V19,
206 Height::Thunder => NetworkVersion::V20,
207 Height::Watermelon => NetworkVersion::V21,
208 Height::WatermelonFix => NetworkVersion::V21,
209 Height::WatermelonFix2 => NetworkVersion::V21,
210 Height::Dragon => NetworkVersion::V22,
211 Height::DragonFix => NetworkVersion::V22,
212 Height::Phoenix => NetworkVersion::V22,
213 Height::Waffle => NetworkVersion::V23,
214 Height::TukTuk => NetworkVersion::V24,
215 Height::Teep => NetworkVersion::V25,
216 Height::Tock => NetworkVersion::V26,
217 Height::TockFix => NetworkVersion::V26,
218 Height::GoldenWeek => NetworkVersion::V27,
219 }
220 }
221}
222
223#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
224#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
225pub struct HeightInfo {
226 pub epoch: ChainEpoch,
227 pub bundle: Option<Cid>,
228}
229
230pub struct HeightInfoWithActorManifest<'a> {
231 #[allow(dead_code)]
232 pub height: Height,
233 pub info: &'a HeightInfo,
234 pub manifest_cid: Cid,
235}
236
237impl<'a> HeightInfoWithActorManifest<'a> {
238 pub fn manifest(&self, store: &impl Blockstore) -> anyhow::Result<BuiltinActorManifest> {
239 BuiltinActorManifest::load_manifest(store, &self.manifest_cid)
240 }
241}
242
243#[derive(Clone)]
244struct DrandPoint<'a> {
245 pub height: ChainEpoch,
246 pub config: &'a LazyLock<DrandConfig<'a>>,
247}
248
249#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
251#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
252#[serde(default)]
253pub struct ChainConfig {
254 pub network: NetworkChain,
255 pub genesis_cid: Option<String>,
256 #[cfg_attr(test, arbitrary(gen(
257 |g: &mut quickcheck::Gen| {
258 let addr = std::net::Ipv4Addr::arbitrary(&mut *g);
259 let n = u8::arbitrary(g) as usize;
260 vec![addr.into(); n]
261 }
262 )))]
263 pub bootstrap_peers: Vec<Multiaddr>,
264 pub block_delay_secs: u32,
265 pub propagation_delay_secs: u32,
266 pub genesis_network: NetworkVersion,
267 pub height_infos: HashMap<Height, HeightInfo>,
268 #[cfg_attr(test, arbitrary(gen(|_g| Policy::default())))]
269 pub policy: Policy,
270 pub eth_chain_id: EthChainId,
271 pub breeze_gas_tamping_duration: i64,
272 pub fip0081_ramp_duration_epochs: u64,
274 pub upgrade_teep_initial_fil_reserved: Option<TokenAmount>,
276 pub f3_enabled: bool,
277 pub f3_consensus: bool,
279 pub f3_bootstrap_epoch: i64,
280 pub f3_initial_power_table: Option<Cid>,
281 pub enable_indexer: bool,
282 pub enable_receipt_event_caching: bool,
283 pub default_max_fee: TokenAmount,
284}
285
286impl ChainConfig {
287 pub fn mainnet() -> Self {
288 use mainnet::*;
289 Self {
290 network: NetworkChain::Mainnet,
291 genesis_cid: Some(GENESIS_CID.to_string()),
292 bootstrap_peers: DEFAULT_BOOTSTRAP.clone(),
293 block_delay_secs: env_or_default(
294 ENV_FOREST_BLOCK_DELAY_SECS,
295 EPOCH_DURATION_SECONDS as u32,
296 ),
297 propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 10),
298 genesis_network: GENESIS_NETWORK_VERSION,
299 height_infos: HEIGHT_INFOS.clone(),
300 policy: make_mainnet_policy!(v13).into(),
301 eth_chain_id: ETH_CHAIN_ID,
302 breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
303 fip0081_ramp_duration_epochs: 365 * EPOCHS_IN_DAY as u64,
305 upgrade_teep_initial_fil_reserved: None,
306 f3_enabled: true,
307 f3_consensus: true,
308 f3_bootstrap_epoch: 4920480,
310 f3_initial_power_table: Some(
311 "bafy2bzacecklgxd2eksmodvhgurqvorkg3wamgqkrunir3al2gchv2cikgmbu"
312 .parse()
313 .expect("invalid f3_initial_power_table"),
314 ),
315 enable_indexer: false,
316 enable_receipt_event_caching: true,
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 enable_receipt_event_caching: true,
354 default_max_fee: TokenAmount::zero(),
355 }
356 }
357
358 pub fn devnet() -> Self {
359 use devnet::*;
360 Self {
361 network: NetworkChain::Devnet("devnet".to_string()),
362 genesis_cid: None,
363 bootstrap_peers: Vec::new(),
364 block_delay_secs: env_or_default(ENV_FOREST_BLOCK_DELAY_SECS, 4),
365 propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 1),
366 genesis_network: *GENESIS_NETWORK_VERSION,
367 height_infos: HEIGHT_INFOS.clone(),
368 policy: make_devnet_policy!(v13).into(),
369 eth_chain_id: ETH_CHAIN_ID,
370 breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
371 fip0081_ramp_duration_epochs: env_or_default(ENV_PLEDGE_RULE_RAMP, 200),
373 upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_400_000_000)),
375 f3_enabled: false,
376 f3_consensus: false,
377 f3_bootstrap_epoch: -1,
378 f3_initial_power_table: None,
379 enable_indexer: false,
380 enable_receipt_event_caching: true,
381 default_max_fee: TokenAmount::zero(),
382 }
383 }
384
385 pub fn butterflynet() -> Self {
386 use butterflynet::*;
387 Self {
388 network: NetworkChain::Butterflynet,
389 genesis_cid: Some(GENESIS_CID.to_string()),
390 bootstrap_peers: DEFAULT_BOOTSTRAP.clone(),
391 block_delay_secs: env_or_default(
392 ENV_FOREST_BLOCK_DELAY_SECS,
393 EPOCH_DURATION_SECONDS as u32,
394 ),
395 propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 6),
396 genesis_network: GENESIS_NETWORK_VERSION,
397 height_infos: HEIGHT_INFOS.clone(),
398 policy: make_butterfly_policy!(v13).into(),
399 eth_chain_id: ETH_CHAIN_ID,
400 breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
401 fip0081_ramp_duration_epochs: env_or_default(
403 ENV_PLEDGE_RULE_RAMP,
404 365 * EPOCHS_IN_DAY as u64,
405 ),
406 upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_600_000_000)),
408 f3_enabled: true,
409 f3_consensus: true,
410 f3_bootstrap_epoch: 1000,
411 f3_initial_power_table: None,
412 enable_indexer: false,
413 enable_receipt_event_caching: true,
414 default_max_fee: TokenAmount::zero(),
415 }
416 }
417
418 pub fn from_chain(network_chain: &NetworkChain) -> Self {
419 match network_chain {
420 NetworkChain::Mainnet => Self::mainnet(),
421 NetworkChain::Calibnet => Self::calibnet(),
422 NetworkChain::Butterflynet => Self::butterflynet(),
423 NetworkChain::Devnet(name) => Self {
424 network: NetworkChain::Devnet(name.clone()),
425 ..Self::devnet()
426 },
427 }
428 }
429
430 fn network_height(&self, epoch: ChainEpoch) -> Option<Height> {
431 self.height_infos
432 .iter()
433 .sorted_by_key(|(_, info)| info.epoch)
434 .rev()
435 .find(|(_, info)| epoch > info.epoch)
436 .map(|(height, _)| *height)
437 }
438
439 pub fn network_height_with_actor_bundle<'a>(
441 &'a self,
442 epoch: ChainEpoch,
443 ) -> Option<HeightInfoWithActorManifest<'a>> {
444 if let Some((height, info, manifest_cid)) = self
445 .height_infos
446 .iter()
447 .sorted_by_key(|(_, info)| info.epoch)
448 .rev()
449 .filter_map(|(height, info)| info.bundle.map(|bundle| (*height, info, bundle)))
450 .find(|(_, info, _)| epoch > info.epoch)
451 {
452 Some(HeightInfoWithActorManifest {
453 height,
454 info,
455 manifest_cid,
456 })
457 } else {
458 None
459 }
460 }
461
462 pub fn network_version(&self, epoch: ChainEpoch) -> NetworkVersion {
465 self.network_height(epoch)
466 .map(NetworkVersion::from)
467 .unwrap_or(self.genesis_network_version())
468 .max(self.genesis_network)
469 }
470
471 pub fn network_version_revision(&self, epoch: ChainEpoch) -> i64 {
474 if let Some(height) = self.network_height(epoch) {
475 let nv = NetworkVersion::from(height);
476 if let Some(rev0_height) = Height::iter().find(|h| NetworkVersion::from(*h) == nv) {
477 return (height as i64) - (rev0_height as i64);
478 }
479 }
480 0
481 }
482
483 pub fn get_beacon_schedule(&self, genesis_ts: u64) -> BeaconSchedule {
484 let ds_iter = match self.network {
485 NetworkChain::Mainnet => mainnet::DRAND_SCHEDULE.iter(),
486 NetworkChain::Calibnet => calibnet::DRAND_SCHEDULE.iter(),
487 NetworkChain::Butterflynet => butterflynet::DRAND_SCHEDULE.iter(),
488 NetworkChain::Devnet(_) => devnet::DRAND_SCHEDULE.iter(),
489 };
490
491 BeaconSchedule(
492 ds_iter
493 .map(|dc| BeaconPoint {
494 height: dc.height,
495 beacon: Box::new(DrandBeacon::new(
496 genesis_ts,
497 self.block_delay_secs as u64,
498 dc.config,
499 )),
500 })
501 .collect(),
502 )
503 }
504
505 pub fn epoch(&self, height: Height) -> ChainEpoch {
506 self.height_infos
507 .iter()
508 .sorted_by_key(|(_, info)| info.epoch)
509 .rev()
510 .find_map(|(infos_height, info)| {
511 if *infos_height == height {
512 Some(info.epoch)
513 } else {
514 None
515 }
516 })
517 .unwrap_or(0)
518 }
519
520 pub async fn genesis_bytes<DB: SettingsStore>(
521 &self,
522 db: &DB,
523 ) -> anyhow::Result<Option<Vec<u8>>> {
524 Ok(match self.network {
525 NetworkChain::Mainnet => Some(mainnet::DEFAULT_GENESIS.to_vec()),
526 NetworkChain::Calibnet => Some(calibnet::DEFAULT_GENESIS.to_vec()),
527 NetworkChain::Butterflynet => Some(butterflynet::fetch_genesis(db).await?),
529 NetworkChain::Devnet(_) => None,
530 })
531 }
532
533 pub fn is_testnet(&self) -> bool {
534 self.network.is_testnet()
535 }
536
537 pub fn is_devnet(&self) -> bool {
538 self.network.is_devnet()
539 }
540
541 pub fn genesis_network_version(&self) -> NetworkVersion {
542 self.genesis_network
543 }
544
545 pub fn initial_fil_reserved(&self, network_version: NetworkVersion) -> &TokenAmount {
546 match &self.upgrade_teep_initial_fil_reserved {
547 Some(fil) if network_version >= NetworkVersion::V25 => fil,
548 _ => &INITIAL_FIL_RESERVED,
549 }
550 }
551
552 pub fn initial_fil_reserved_at_height(&self, height: i64) -> &TokenAmount {
553 let network_version = self.network_version(height);
554 self.initial_fil_reserved(network_version)
555 }
556}
557
558impl Default for ChainConfig {
559 fn default() -> Self {
560 ChainConfig::mainnet()
561 }
562}
563
564pub(crate) fn parse_bootstrap_peers(bootstrap_peer_list: &str) -> Vec<Multiaddr> {
565 bootstrap_peer_list
566 .split('\n')
567 .filter(|s| !s.is_empty())
568 .map(|s| {
569 Multiaddr::from_str(s).unwrap_or_else(|e| panic!("invalid bootstrap peer {s}: {e}"))
570 })
571 .collect()
572}
573
574#[allow(dead_code)]
575fn get_upgrade_epoch_by_height<'a>(
576 mut height_infos: impl Iterator<Item = &'a (Height, HeightInfo)>,
577 height: Height,
578) -> Option<ChainEpoch> {
579 height_infos.find_map(|(infos_height, info)| {
580 if *infos_height == height {
581 Some(info.epoch)
582 } else {
583 None
584 }
585 })
586}
587
588fn get_upgrade_height_from_env(env_var_key: &str) -> Option<ChainEpoch> {
589 if let Ok(value) = std::env::var(env_var_key) {
590 if let Ok(epoch) = value.parse() {
591 return Some(epoch);
592 } else {
593 warn!("Failed to parse {env_var_key}={value}, value should be an integer");
594 }
595 }
596 None
597}
598
599#[macro_export]
600macro_rules! make_height {
601 ($id:ident,$epoch:expr) => {
602 (
603 Height::$id,
604 HeightInfo {
605 epoch: $epoch,
606 bundle: None,
607 },
608 )
609 };
610 ($id:ident,$epoch:expr,$bundle:expr) => {
611 (
612 Height::$id,
613 HeightInfo {
614 epoch: $epoch,
615 bundle: Some(Cid::try_from($bundle).unwrap()),
616 },
617 )
618 };
619}
620
621pub fn calculate_expected_epoch(
628 now_timestamp: u64,
629 genesis_timestamp: u64,
630 block_delay_secs: u32,
631) -> i64 {
632 (now_timestamp.saturating_sub(genesis_timestamp) / block_delay_secs as u64) as i64
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 fn heights_are_present(height_infos: &HashMap<Height, HeightInfo>) {
640 const REQUIRED_HEIGHTS: [Height; 30] = [
643 Height::Breeze,
644 Height::Smoke,
645 Height::Ignition,
646 Height::Refuel,
647 Height::Assembly,
648 Height::Tape,
649 Height::Liftoff,
650 Height::Kumquat,
651 Height::Calico,
652 Height::Persian,
653 Height::Orange,
654 Height::Claus,
655 Height::Trust,
656 Height::Norwegian,
657 Height::Turbo,
658 Height::Hyperdrive,
659 Height::Chocolate,
660 Height::OhSnap,
661 Height::Skyr,
662 Height::Shark,
663 Height::Hygge,
664 Height::Lightning,
665 Height::Thunder,
666 Height::Watermelon,
667 Height::Dragon,
668 Height::Phoenix,
669 Height::Waffle,
670 Height::TukTuk,
671 Height::Teep,
672 Height::GoldenWeek,
673 ];
674
675 for height in &REQUIRED_HEIGHTS {
676 assert!(height_infos.get(height).is_some());
677 }
678 }
679
680 #[test]
681 fn test_mainnet_heights() {
682 heights_are_present(&mainnet::HEIGHT_INFOS);
683 }
684
685 #[test]
686 fn test_calibnet_heights() {
687 heights_are_present(&calibnet::HEIGHT_INFOS);
688 }
689
690 #[test]
691 fn test_devnet_heights() {
692 heights_are_present(&devnet::HEIGHT_INFOS);
693 }
694
695 #[test]
696 fn test_butterflynet_heights() {
697 heights_are_present(&butterflynet::HEIGHT_INFOS);
698 }
699
700 #[test]
701 fn test_get_upgrade_height_no_env_var() {
702 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_1");
703 assert_eq!(epoch, None);
704 }
705
706 #[test]
707 fn test_get_upgrade_height_valid_env_var() {
708 unsafe { std::env::set_var("FOREST_TEST_VAR_2", "10") };
709 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_2");
710 assert_eq!(epoch, Some(10));
711 }
712
713 #[test]
714 fn test_get_upgrade_height_invalid_env_var() {
715 unsafe { std::env::set_var("FOREST_TEST_VAR_3", "foo") };
716 let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_3");
717 assert_eq!(epoch, None);
718 }
719
720 #[test]
721 fn test_calculate_expected_epoch() {
722 assert_eq!(0, calculate_expected_epoch(0, 0, 1));
724 assert_eq!(5, calculate_expected_epoch(5, 0, 1));
725
726 let mainnet_genesis = 1598306400;
727 let mainnet_block_delay = 30;
728
729 assert_eq!(
730 0,
731 calculate_expected_epoch(mainnet_genesis, mainnet_genesis, mainnet_block_delay)
732 );
733
734 assert_eq!(
735 0,
736 calculate_expected_epoch(
737 mainnet_genesis + mainnet_block_delay as u64 - 1,
738 mainnet_genesis,
739 mainnet_block_delay
740 )
741 );
742
743 assert_eq!(
744 1,
745 calculate_expected_epoch(
746 mainnet_genesis + mainnet_block_delay as u64,
747 mainnet_genesis,
748 mainnet_block_delay
749 )
750 );
751 }
752
753 #[test]
754 fn network_chain_display() {
755 assert_eq!(
756 NetworkChain::Mainnet.to_string(),
757 mainnet::NETWORK_COMMON_NAME
758 );
759 assert_eq!(
760 NetworkChain::Calibnet.to_string(),
761 calibnet::NETWORK_COMMON_NAME
762 );
763 assert_eq!(
764 NetworkChain::Butterflynet.to_string(),
765 butterflynet::NETWORK_COMMON_NAME
766 );
767 assert_eq!(
768 NetworkChain::Devnet("dummydevnet".into()).to_string(),
769 "dummydevnet"
770 );
771 }
772
773 #[test]
774 fn chain_config() {
775 ChainConfig::mainnet();
776 ChainConfig::calibnet();
777 ChainConfig::devnet();
778 ChainConfig::butterflynet();
779 }
780
781 #[test]
782 fn network_version() {
783 let cfg = ChainConfig::calibnet();
784 assert_eq!(cfg.network_version(1_013_134 - 1), NetworkVersion::V20);
785 assert_eq!(cfg.network_version(1_013_134), NetworkVersion::V20);
786 assert_eq!(cfg.network_version(1_013_134 + 1), NetworkVersion::V21);
787 assert_eq!(cfg.network_version_revision(1_013_134 + 1), 0);
788 assert_eq!(cfg.network_version(1_070_494), NetworkVersion::V21);
789 assert_eq!(cfg.network_version_revision(1_070_494), 0);
790 assert_eq!(cfg.network_version(1_070_494 + 1), NetworkVersion::V21);
791 assert_eq!(cfg.network_version_revision(1_070_494 + 1), 1);
792 }
793
794 #[test]
795 fn test_network_height_with_actor_bundle() {
796 let cfg = ChainConfig::mainnet();
797 let info = cfg.network_height_with_actor_bundle(5_348_280 + 1).unwrap();
798 assert_eq!(info.height, Height::GoldenWeek);
799 let info = cfg.network_height_with_actor_bundle(5_348_280).unwrap();
800 assert_eq!(info.height, Height::Teep);
802 let info = cfg.network_height_with_actor_bundle(5_348_280 - 1).unwrap();
803 assert_eq!(info.height, Height::Teep);
804 assert!(cfg.network_height_with_actor_bundle(1).is_none());
805 assert!(cfg.network_height_with_actor_bundle(0).is_none());
806 }
807}