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