forest/networks/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::str::FromStr;
5use std::sync::LazyLock;
6
7use ahash::HashMap;
8use cid::Cid;
9use fil_actors_shared::v13::runtime::Policy;
10use fvm_ipld_blockstore::Blockstore;
11use itertools::Itertools;
12use libp2p::Multiaddr;
13use num_traits::Zero;
14use serde::{Deserialize, Serialize};
15use strum::IntoEnumIterator;
16use strum_macros::Display;
17use strum_macros::EnumIter;
18use tracing::warn;
19
20use crate::beacon::{BeaconPoint, BeaconSchedule, DrandBeacon, DrandConfig};
21use crate::db::SettingsStore;
22use crate::eth::EthChainId;
23use crate::shim::clock::{ChainEpoch, EPOCH_DURATION_SECONDS, EPOCHS_IN_DAY};
24use crate::shim::econ::TokenAmount;
25use crate::shim::machine::BuiltinActorManifest;
26use crate::shim::sector::{RegisteredPoStProofV3, RegisteredSealProofV3};
27use crate::shim::version::NetworkVersion;
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
50/// Newest network version for all networks
51pub 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/// Forest builtin `filecoin` network chains. In general only `mainnet` and its
61/// chain information should be considered stable.
62#[derive(
63    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash, displaydoc::Display,
64)]
65#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
66#[serde(tag = "type", content = "name", rename_all = "lowercase")]
67pub enum NetworkChain {
68    /// mainnet
69    #[default]
70    Mainnet,
71    /// calibnet
72    Calibnet,
73    /// butterflynet
74    Butterflynet,
75    /// devnet
76    #[displaydoc("{0}")]
77    Devnet(String),
78}
79
80impl FromStr for NetworkChain {
81    type Err = anyhow::Error;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        match s {
85            mainnet::NETWORK_COMMON_NAME | mainnet::NETWORK_GENESIS_NAME => {
86                Ok(NetworkChain::Mainnet)
87            }
88            calibnet::NETWORK_COMMON_NAME | calibnet::NETWORK_GENESIS_NAME => {
89                Ok(NetworkChain::Calibnet)
90            }
91            butterflynet::NETWORK_COMMON_NAME => Ok(NetworkChain::Butterflynet),
92            name => Ok(NetworkChain::Devnet(name.to_owned())),
93        }
94    }
95}
96
97impl NetworkChain {
98    /// Returns the `NetworkChain`s internal name as set in the genesis block, which is not the
99    /// same as the recent state network name.
100    ///
101    /// As a rule of thumb, the internal name should be used when interacting with
102    /// protocol internals and P2P.
103    pub fn genesis_name(&self) -> GenesisNetworkName {
104        match self {
105            NetworkChain::Mainnet => mainnet::NETWORK_GENESIS_NAME.into(),
106            NetworkChain::Calibnet => calibnet::NETWORK_GENESIS_NAME.into(),
107            _ => self.to_string().into(),
108        }
109    }
110    /// Returns [`NetworkChain::Calibnet`] or [`NetworkChain::Mainnet`] if `cid`
111    /// is the hard-coded genesis CID for either of those networks.
112    pub fn from_genesis(cid: &Cid) -> Option<Self> {
113        if cid == &*mainnet::GENESIS_CID {
114            Some(Self::Mainnet)
115        } else if cid == &*calibnet::GENESIS_CID {
116            Some(Self::Calibnet)
117        } else if cid == &*butterflynet::GENESIS_CID {
118            Some(Self::Butterflynet)
119        } else {
120            None
121        }
122    }
123
124    /// Returns [`NetworkChain::Calibnet`] or [`NetworkChain::Mainnet`] if `cid`
125    /// is the hard-coded genesis CID for either of those networks.
126    ///
127    /// Else returns a [`NetworkChain::Devnet`] with a placeholder name.
128    pub fn from_genesis_or_devnet_placeholder(cid: &Cid) -> Self {
129        Self::from_genesis(cid).unwrap_or(Self::Devnet(String::from("devnet")))
130    }
131
132    pub fn is_testnet(&self) -> bool {
133        !matches!(self, NetworkChain::Mainnet)
134    }
135
136    pub fn is_devnet(&self) -> bool {
137        matches!(self, NetworkChain::Devnet(..))
138    }
139}
140
141/// Defines the meaningful heights of the protocol.
142#[derive(
143    Debug, Default, Display, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, EnumIter,
144)]
145#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
146pub enum Height {
147    #[default]
148    Breeze,
149    Smoke,
150    Ignition,
151    Refuel,
152    Assembly,
153    Tape,
154    Liftoff,
155    Kumquat,
156    Calico,
157    Persian,
158    Orange,
159    Claus,
160    Trust,
161    Norwegian,
162    Turbo,
163    Hyperdrive,
164    Chocolate,
165    OhSnap,
166    Skyr,
167    Shark,
168    Hygge,
169    Lightning,
170    Thunder,
171    Watermelon,
172    WatermelonFix,
173    WatermelonFix2,
174    Dragon,
175    DragonFix,
176    Phoenix,
177    Waffle,
178    TukTuk,
179    Teep,
180    Tock,
181    TockFix,
182    GoldenWeek,
183}
184
185impl From<Height> for NetworkVersion {
186    fn from(height: Height) -> NetworkVersion {
187        match height {
188            Height::Breeze => NetworkVersion::V1,
189            Height::Smoke => NetworkVersion::V2,
190            Height::Ignition => NetworkVersion::V3,
191            Height::Refuel => NetworkVersion::V3,
192            Height::Assembly => NetworkVersion::V4,
193            Height::Tape => NetworkVersion::V5,
194            Height::Liftoff => NetworkVersion::V5,
195            Height::Kumquat => NetworkVersion::V6,
196            Height::Calico => NetworkVersion::V7,
197            Height::Persian => NetworkVersion::V8,
198            Height::Orange => NetworkVersion::V9,
199            Height::Claus => NetworkVersion::V9,
200            Height::Trust => NetworkVersion::V10,
201            Height::Norwegian => NetworkVersion::V11,
202            Height::Turbo => NetworkVersion::V12,
203            Height::Hyperdrive => NetworkVersion::V13,
204            Height::Chocolate => NetworkVersion::V14,
205            Height::OhSnap => NetworkVersion::V15,
206            Height::Skyr => NetworkVersion::V16,
207            Height::Shark => NetworkVersion::V17,
208            Height::Hygge => NetworkVersion::V18,
209            Height::Lightning => NetworkVersion::V19,
210            Height::Thunder => NetworkVersion::V20,
211            Height::Watermelon => NetworkVersion::V21,
212            Height::WatermelonFix => NetworkVersion::V21,
213            Height::WatermelonFix2 => NetworkVersion::V21,
214            Height::Dragon => NetworkVersion::V22,
215            Height::DragonFix => NetworkVersion::V22,
216            Height::Phoenix => NetworkVersion::V22,
217            Height::Waffle => NetworkVersion::V23,
218            Height::TukTuk => NetworkVersion::V24,
219            Height::Teep => NetworkVersion::V25,
220            Height::Tock => NetworkVersion::V26,
221            Height::TockFix => NetworkVersion::V26,
222            Height::GoldenWeek => NetworkVersion::V27,
223        }
224    }
225}
226
227#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
228#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
229pub struct HeightInfo {
230    pub epoch: ChainEpoch,
231    pub bundle: Option<Cid>,
232}
233
234pub struct HeightInfoWithActorManifest<'a> {
235    #[allow(dead_code)]
236    pub height: Height,
237    pub info: &'a HeightInfo,
238    pub manifest_cid: Cid,
239}
240
241impl<'a> HeightInfoWithActorManifest<'a> {
242    pub fn manifest(&self, store: &impl Blockstore) -> anyhow::Result<BuiltinActorManifest> {
243        BuiltinActorManifest::load_manifest(store, &self.manifest_cid)
244    }
245}
246
247#[derive(Clone)]
248struct DrandPoint<'a> {
249    pub height: ChainEpoch,
250    pub config: &'a LazyLock<DrandConfig<'a>>,
251}
252
253/// Defines all network configuration parameters.
254#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
255#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
256#[serde(default)]
257pub struct ChainConfig {
258    pub network: NetworkChain,
259    pub genesis_cid: Option<String>,
260    #[cfg_attr(test, arbitrary(gen(
261        |g: &mut quickcheck::Gen| {
262            let addr = std::net::Ipv4Addr::arbitrary(&mut *g);
263            let n = u8::arbitrary(g) as usize;
264            vec![addr.into(); n]
265        }
266    )))]
267    pub bootstrap_peers: Vec<Multiaddr>,
268    pub block_delay_secs: u32,
269    pub propagation_delay_secs: u32,
270    pub genesis_network: NetworkVersion,
271    pub height_infos: HashMap<Height, HeightInfo>,
272    #[cfg_attr(test, arbitrary(gen(|_g| Policy::default())))]
273    pub policy: Policy,
274    pub eth_chain_id: EthChainId,
275    pub breeze_gas_tamping_duration: i64,
276    // FIP0081 gradually comes into effect over this many epochs.
277    pub fip0081_ramp_duration_epochs: u64,
278    // See FIP-0100 and https://github.com/filecoin-project/lotus/pull/12938 for why this exists
279    pub upgrade_teep_initial_fil_reserved: Option<TokenAmount>,
280    pub f3_enabled: bool,
281    // F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This flag has no effect if F3 is not enabled.
282    pub f3_consensus: bool,
283    pub f3_bootstrap_epoch: i64,
284    pub f3_initial_power_table: Option<Cid>,
285    pub enable_indexer: bool,
286    pub enable_receipt_event_caching: bool,
287    pub default_max_fee: TokenAmount,
288}
289
290impl ChainConfig {
291    pub fn mainnet() -> Self {
292        use mainnet::*;
293        Self {
294            network: NetworkChain::Mainnet,
295            genesis_cid: Some(GENESIS_CID.to_string()),
296            bootstrap_peers: DEFAULT_BOOTSTRAP.clone(),
297            block_delay_secs: env_or_default(
298                ENV_FOREST_BLOCK_DELAY_SECS,
299                EPOCH_DURATION_SECONDS as u32,
300            ),
301            propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 10),
302            genesis_network: GENESIS_NETWORK_VERSION,
303            height_infos: HEIGHT_INFOS.clone(),
304            policy: make_mainnet_policy!(v13),
305            eth_chain_id: ETH_CHAIN_ID,
306            breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
307            // 1 year on mainnet
308            fip0081_ramp_duration_epochs: 365 * EPOCHS_IN_DAY as u64,
309            upgrade_teep_initial_fil_reserved: None,
310            f3_enabled: true,
311            f3_consensus: true,
312            // April 29 at 10:00 UTC
313            f3_bootstrap_epoch: 4920480,
314            f3_initial_power_table: Some(
315                "bafy2bzacecklgxd2eksmodvhgurqvorkg3wamgqkrunir3al2gchv2cikgmbu"
316                    .parse()
317                    .expect("invalid f3_initial_power_table"),
318            ),
319            enable_indexer: false,
320            enable_receipt_event_caching: true,
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.clone(),
338            policy: make_calibnet_policy!(v13),
339            eth_chain_id: ETH_CHAIN_ID,
340            breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
341            // 3 days on calibnet
342            fip0081_ramp_duration_epochs: 3 * EPOCHS_IN_DAY as u64,
343            // FIP-0100: 300M -> 1.2B FIL
344            upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_200_000_000)),
345            // Enable after `f3_initial_power_table` is determined and set to avoid GC hell
346            // (state tree of epoch 2_081_674 - 900 has to be present in the database if `f3_initial_power_table` is not set)
347            f3_enabled: true,
348            f3_consensus: true,
349            // 2024-10-24T13:30:00Z
350            f3_bootstrap_epoch: 2_081_674,
351            f3_initial_power_table: Some(
352                "bafy2bzaceab236vmmb3n4q4tkvua2n4dphcbzzxerxuey3mot4g3cov5j3r2c"
353                    .parse()
354                    .expect("invalid f3_initial_power_table"),
355            ),
356            enable_indexer: false,
357            enable_receipt_event_caching: true,
358            default_max_fee: TokenAmount::zero(),
359        }
360    }
361
362    pub fn devnet() -> Self {
363        use devnet::*;
364        Self {
365            network: NetworkChain::Devnet("devnet".to_string()),
366            genesis_cid: None,
367            bootstrap_peers: Vec::new(),
368            block_delay_secs: env_or_default(ENV_FOREST_BLOCK_DELAY_SECS, 4),
369            propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 1),
370            genesis_network: *GENESIS_NETWORK_VERSION,
371            height_infos: HEIGHT_INFOS.clone(),
372            policy: make_devnet_policy!(v13),
373            eth_chain_id: ETH_CHAIN_ID,
374            breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
375            // Devnet ramp is 200 epochs in Lotus (subject to change).
376            fip0081_ramp_duration_epochs: env_or_default(ENV_PLEDGE_RULE_RAMP, 200),
377            // FIP-0100: 300M -> 1.4B FIL
378            upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_400_000_000)),
379            f3_enabled: false,
380            f3_consensus: false,
381            f3_bootstrap_epoch: -1,
382            f3_initial_power_table: None,
383            enable_indexer: false,
384            enable_receipt_event_caching: true,
385            default_max_fee: TokenAmount::zero(),
386        }
387    }
388
389    pub fn butterflynet() -> Self {
390        use butterflynet::*;
391        Self {
392            network: NetworkChain::Butterflynet,
393            genesis_cid: Some(GENESIS_CID.to_string()),
394            bootstrap_peers: DEFAULT_BOOTSTRAP.clone(),
395            block_delay_secs: env_or_default(
396                ENV_FOREST_BLOCK_DELAY_SECS,
397                EPOCH_DURATION_SECONDS as u32,
398            ),
399            propagation_delay_secs: env_or_default(ENV_FOREST_PROPAGATION_DELAY_SECS, 6),
400            genesis_network: GENESIS_NETWORK_VERSION,
401            height_infos: HEIGHT_INFOS.clone(),
402            policy: make_butterfly_policy!(v13),
403            eth_chain_id: ETH_CHAIN_ID,
404            breeze_gas_tamping_duration: BREEZE_GAS_TAMPING_DURATION,
405            // Butterflynet ramp is current set to 365 days in Lotus but this may change.
406            fip0081_ramp_duration_epochs: env_or_default(
407                ENV_PLEDGE_RULE_RAMP,
408                365 * EPOCHS_IN_DAY as u64,
409            ),
410            // FIP-0100: 300M -> 1.6B FIL
411            upgrade_teep_initial_fil_reserved: Some(TokenAmount::from_whole(1_600_000_000)),
412            f3_enabled: true,
413            f3_consensus: true,
414            f3_bootstrap_epoch: 1000,
415            f3_initial_power_table: None,
416            enable_indexer: false,
417            enable_receipt_event_caching: true,
418            default_max_fee: TokenAmount::zero(),
419        }
420    }
421
422    pub fn from_chain(network_chain: &NetworkChain) -> Self {
423        match network_chain {
424            NetworkChain::Mainnet => Self::mainnet(),
425            NetworkChain::Calibnet => Self::calibnet(),
426            NetworkChain::Butterflynet => Self::butterflynet(),
427            NetworkChain::Devnet(name) => Self {
428                network: NetworkChain::Devnet(name.clone()),
429                ..Self::devnet()
430            },
431        }
432    }
433
434    fn network_height(&self, epoch: ChainEpoch) -> Option<Height> {
435        self.height_infos
436            .iter()
437            .sorted_by_key(|(_, info)| info.epoch)
438            .rev()
439            .find(|(_, info)| epoch > info.epoch)
440            .map(|(height, _)| *height)
441    }
442
443    /// Gets the latest network height prior to the given epoch that upgrades the actor bundle
444    pub fn network_height_with_actor_bundle<'a>(
445        &'a self,
446        epoch: ChainEpoch,
447    ) -> Option<HeightInfoWithActorManifest<'a>> {
448        if let Some((height, info, manifest_cid)) = self
449            .height_infos
450            .iter()
451            .sorted_by_key(|(_, info)| info.epoch)
452            .rev()
453            .filter_map(|(height, info)| info.bundle.map(|bundle| (*height, info, bundle)))
454            .find(|(_, info, _)| epoch > info.epoch)
455        {
456            Some(HeightInfoWithActorManifest {
457                height,
458                info,
459                manifest_cid,
460            })
461        } else {
462            None
463        }
464    }
465
466    /// Returns the network version at the given epoch.
467    /// If the epoch is before the first upgrade, the genesis network version is returned.
468    pub fn network_version(&self, epoch: ChainEpoch) -> NetworkVersion {
469        self.network_height(epoch)
470            .map(NetworkVersion::from)
471            .unwrap_or(self.genesis_network_version())
472            .max(self.genesis_network)
473    }
474
475    /// Returns the network version revision at the given epoch for distinguishing network upgrades
476    /// that do not bump the network version.
477    pub fn network_version_revision(&self, epoch: ChainEpoch) -> i64 {
478        if let Some(height) = self.network_height(epoch) {
479            let nv = NetworkVersion::from(height);
480            if let Some(rev0_height) = Height::iter().find(|h| NetworkVersion::from(*h) == nv) {
481                return (height as i64) - (rev0_height as i64);
482            }
483        }
484        0
485    }
486
487    pub fn get_beacon_schedule(&self, genesis_ts: u64) -> BeaconSchedule {
488        let ds_iter = match self.network {
489            NetworkChain::Mainnet => mainnet::DRAND_SCHEDULE.iter(),
490            NetworkChain::Calibnet => calibnet::DRAND_SCHEDULE.iter(),
491            NetworkChain::Butterflynet => butterflynet::DRAND_SCHEDULE.iter(),
492            NetworkChain::Devnet(_) => devnet::DRAND_SCHEDULE.iter(),
493        };
494
495        BeaconSchedule(
496            ds_iter
497                .map(|dc| BeaconPoint {
498                    height: dc.height,
499                    beacon: Box::new(DrandBeacon::new(
500                        genesis_ts,
501                        self.block_delay_secs as u64,
502                        dc.config,
503                    )),
504                })
505                .collect(),
506        )
507    }
508
509    pub fn epoch(&self, height: Height) -> ChainEpoch {
510        self.height_infos
511            .iter()
512            .sorted_by_key(|(_, info)| info.epoch)
513            .rev()
514            .find_map(|(infos_height, info)| {
515                if *infos_height == height {
516                    Some(info.epoch)
517                } else {
518                    None
519                }
520            })
521            .unwrap_or(0)
522    }
523
524    pub async fn genesis_bytes<DB: SettingsStore>(
525        &self,
526        db: &DB,
527    ) -> anyhow::Result<Option<Vec<u8>>> {
528        Ok(match self.network {
529            NetworkChain::Mainnet => Some(mainnet::DEFAULT_GENESIS.to_vec()),
530            NetworkChain::Calibnet => Some(calibnet::DEFAULT_GENESIS.to_vec()),
531            // Butterflynet genesis is not hardcoded in the binary, for size reasons.
532            NetworkChain::Butterflynet => Some(butterflynet::fetch_genesis(db).await?),
533            NetworkChain::Devnet(_) => None,
534        })
535    }
536
537    pub fn is_testnet(&self) -> bool {
538        self.network.is_testnet()
539    }
540
541    pub fn is_devnet(&self) -> bool {
542        self.network.is_devnet()
543    }
544
545    pub fn genesis_network_version(&self) -> NetworkVersion {
546        self.genesis_network
547    }
548
549    pub fn initial_fil_reserved(&self, network_version: NetworkVersion) -> &TokenAmount {
550        match &self.upgrade_teep_initial_fil_reserved {
551            Some(fil) if network_version >= NetworkVersion::V25 => fil,
552            _ => &INITIAL_FIL_RESERVED,
553        }
554    }
555
556    pub fn initial_fil_reserved_at_height(&self, height: i64) -> &TokenAmount {
557        let network_version = self.network_version(height);
558        self.initial_fil_reserved(network_version)
559    }
560}
561
562impl Default for ChainConfig {
563    fn default() -> Self {
564        ChainConfig::mainnet()
565    }
566}
567
568pub(crate) fn parse_bootstrap_peers(bootstrap_peer_list: &str) -> Vec<Multiaddr> {
569    bootstrap_peer_list
570        .split('\n')
571        .filter(|s| !s.is_empty())
572        .map(|s| {
573            Multiaddr::from_str(s).unwrap_or_else(|e| panic!("invalid bootstrap peer {s}: {e}"))
574        })
575        .collect()
576}
577
578#[allow(dead_code)]
579fn get_upgrade_epoch_by_height<'a>(
580    mut height_infos: impl Iterator<Item = &'a (Height, HeightInfo)>,
581    height: Height,
582) -> Option<ChainEpoch> {
583    height_infos.find_map(|(infos_height, info)| {
584        if *infos_height == height {
585            Some(info.epoch)
586        } else {
587            None
588        }
589    })
590}
591
592fn get_upgrade_height_from_env(env_var_key: &str) -> Option<ChainEpoch> {
593    if let Ok(value) = std::env::var(env_var_key) {
594        if let Ok(epoch) = value.parse() {
595            return Some(epoch);
596        } else {
597            warn!("Failed to parse {env_var_key}={value}, value should be an integer");
598        }
599    }
600    None
601}
602
603#[macro_export]
604macro_rules! make_height {
605    ($id:ident,$epoch:expr) => {
606        (
607            Height::$id,
608            HeightInfo {
609                epoch: $epoch,
610                bundle: None,
611            },
612        )
613    };
614    ($id:ident,$epoch:expr,$bundle:expr) => {
615        (
616            Height::$id,
617            HeightInfo {
618                epoch: $epoch,
619                bundle: Some(Cid::try_from($bundle).unwrap()),
620            },
621        )
622    };
623}
624
625// The formula matches lotus
626// ```go
627// sinceGenesis := build.Clock.Now().Sub(genesisTime)
628// expectedHeight := int64(sinceGenesis.Seconds()) / int64(build.BlockDelaySecs)
629// ```
630// See <https://github.com/filecoin-project/lotus/blob/b27c861485695d3f5bb92bcb281abc95f4d90fb6/chain/sync.go#L180>
631pub fn calculate_expected_epoch(
632    now_timestamp: u64,
633    genesis_timestamp: u64,
634    block_delay_secs: u32,
635) -> i64 {
636    (now_timestamp.saturating_sub(genesis_timestamp) / block_delay_secs as u64) as i64
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642
643    fn heights_are_present(height_infos: &HashMap<Height, HeightInfo>) {
644        /// These are required heights that need to be defined for all networks, for, e.g., conformance
645        /// with `Filecoin.StateGetNetworkParams` RPC method.
646        const REQUIRED_HEIGHTS: [Height; 30] = [
647            Height::Breeze,
648            Height::Smoke,
649            Height::Ignition,
650            Height::Refuel,
651            Height::Assembly,
652            Height::Tape,
653            Height::Liftoff,
654            Height::Kumquat,
655            Height::Calico,
656            Height::Persian,
657            Height::Orange,
658            Height::Claus,
659            Height::Trust,
660            Height::Norwegian,
661            Height::Turbo,
662            Height::Hyperdrive,
663            Height::Chocolate,
664            Height::OhSnap,
665            Height::Skyr,
666            Height::Shark,
667            Height::Hygge,
668            Height::Lightning,
669            Height::Thunder,
670            Height::Watermelon,
671            Height::Dragon,
672            Height::Phoenix,
673            Height::Waffle,
674            Height::TukTuk,
675            Height::Teep,
676            Height::GoldenWeek,
677        ];
678
679        for height in &REQUIRED_HEIGHTS {
680            assert!(height_infos.get(height).is_some());
681        }
682    }
683
684    #[test]
685    fn test_mainnet_heights() {
686        heights_are_present(&mainnet::HEIGHT_INFOS);
687    }
688
689    #[test]
690    fn test_calibnet_heights() {
691        heights_are_present(&calibnet::HEIGHT_INFOS);
692    }
693
694    #[test]
695    fn test_devnet_heights() {
696        heights_are_present(&devnet::HEIGHT_INFOS);
697    }
698
699    #[test]
700    fn test_butterflynet_heights() {
701        heights_are_present(&butterflynet::HEIGHT_INFOS);
702    }
703
704    #[test]
705    fn test_get_upgrade_height_no_env_var() {
706        let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_1");
707        assert_eq!(epoch, None);
708    }
709
710    #[test]
711    fn test_get_upgrade_height_valid_env_var() {
712        unsafe { std::env::set_var("FOREST_TEST_VAR_2", "10") };
713        let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_2");
714        assert_eq!(epoch, Some(10));
715    }
716
717    #[test]
718    fn test_get_upgrade_height_invalid_env_var() {
719        unsafe { std::env::set_var("FOREST_TEST_VAR_3", "foo") };
720        let epoch = get_upgrade_height_from_env("FOREST_TEST_VAR_3");
721        assert_eq!(epoch, None);
722    }
723
724    #[test]
725    fn test_calculate_expected_epoch() {
726        // now, genesis, block_delay
727        assert_eq!(0, calculate_expected_epoch(0, 0, 1));
728        assert_eq!(5, calculate_expected_epoch(5, 0, 1));
729
730        let mainnet_genesis = 1598306400;
731        let mainnet_block_delay = 30;
732
733        assert_eq!(
734            0,
735            calculate_expected_epoch(mainnet_genesis, mainnet_genesis, mainnet_block_delay)
736        );
737
738        assert_eq!(
739            0,
740            calculate_expected_epoch(
741                mainnet_genesis + mainnet_block_delay as u64 - 1,
742                mainnet_genesis,
743                mainnet_block_delay
744            )
745        );
746
747        assert_eq!(
748            1,
749            calculate_expected_epoch(
750                mainnet_genesis + mainnet_block_delay as u64,
751                mainnet_genesis,
752                mainnet_block_delay
753            )
754        );
755    }
756
757    #[test]
758    fn network_chain_display() {
759        assert_eq!(
760            NetworkChain::Mainnet.to_string(),
761            mainnet::NETWORK_COMMON_NAME
762        );
763        assert_eq!(
764            NetworkChain::Calibnet.to_string(),
765            calibnet::NETWORK_COMMON_NAME
766        );
767        assert_eq!(
768            NetworkChain::Butterflynet.to_string(),
769            butterflynet::NETWORK_COMMON_NAME
770        );
771        assert_eq!(
772            NetworkChain::Devnet("dummydevnet".into()).to_string(),
773            "dummydevnet"
774        );
775    }
776
777    #[test]
778    fn chain_config() {
779        ChainConfig::mainnet();
780        ChainConfig::calibnet();
781        ChainConfig::devnet();
782        ChainConfig::butterflynet();
783    }
784
785    #[test]
786    fn network_version() {
787        let cfg = ChainConfig::calibnet();
788        assert_eq!(cfg.network_version(1_013_134 - 1), NetworkVersion::V20);
789        assert_eq!(cfg.network_version(1_013_134), NetworkVersion::V20);
790        assert_eq!(cfg.network_version(1_013_134 + 1), NetworkVersion::V21);
791        assert_eq!(cfg.network_version_revision(1_013_134 + 1), 0);
792        assert_eq!(cfg.network_version(1_070_494), NetworkVersion::V21);
793        assert_eq!(cfg.network_version_revision(1_070_494), 0);
794        assert_eq!(cfg.network_version(1_070_494 + 1), NetworkVersion::V21);
795        assert_eq!(cfg.network_version_revision(1_070_494 + 1), 1);
796    }
797
798    #[test]
799    fn test_network_height_with_actor_bundle() {
800        let cfg = ChainConfig::mainnet();
801        let info = cfg.network_height_with_actor_bundle(5_348_280 + 1).unwrap();
802        assert_eq!(info.height, Height::GoldenWeek);
803        let info = cfg.network_height_with_actor_bundle(5_348_280).unwrap();
804        // No actor bundle for Tock, so it should be Teep
805        assert_eq!(info.height, Height::Teep);
806        let info = cfg.network_height_with_actor_bundle(5_348_280 - 1).unwrap();
807        assert_eq!(info.height, Height::Teep);
808        assert!(cfg.network_height_with_actor_bundle(1).is_none());
809        assert!(cfg.network_height_with_actor_bundle(0).is_none());
810    }
811}