forest/networks/
mod.rs

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