Skip to main content

forest/f3/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4#![allow(clippy::too_many_arguments)]
5
6#[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))]
7mod go_ffi;
8use std::path::{Path, PathBuf};
9
10#[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))]
11use go_ffi::*;
12
13pub mod snapshot;
14
15use cid::Cid;
16
17use crate::{networks::ChainConfig, utils::misc::env::is_env_set_and_truthy};
18
19#[derive(Debug, Clone, Eq, PartialEq)]
20pub struct F3Options {
21    pub chain_finality: i64,
22    pub bootstrap_epoch: i64,
23    pub initial_power_table: Option<Cid>,
24}
25
26pub fn get_f3_root(config: &crate::Config) -> PathBuf {
27    std::env::var("FOREST_F3_ROOT")
28        .map(|p| Path::new(&p).to_path_buf())
29        .unwrap_or_else(|_| {
30            config
31                .client
32                .data_dir
33                .join(format!("f3/{}", config.chain()))
34        })
35}
36
37pub fn get_f3_sidecar_params(chain_config: &ChainConfig) -> F3Options {
38    let chain_finality = std::env::var("FOREST_F3_FINALITY")
39        .ok()
40        .and_then(|v| match v.parse::<i64>() {
41            Ok(f) if f > 0 => Some(f),
42            _ => {
43                tracing::warn!(
44                    "Invalid FOREST_F3_FINALITY value {v}. A positive integer is expected."
45                );
46                None
47            }
48        })
49        .inspect(|i| {
50            tracing::info!("Using F3 finality {i} set by FOREST_F3_FINALITY");
51        })
52        .unwrap_or(chain_config.policy.chain_finality);
53    // This will be used post-bootstrap to hard-code the initial F3's initial power table CID.
54    // Read from an environment variable for now before the hard-coded value is determined.
55    let initial_power_table = match std::env::var("FOREST_F3_INITIAL_POWER_TABLE") {
56        Ok(i) if i.is_empty() => {
57            tracing::info!("F3 initial power table cid is unset by FOREST_F3_INITIAL_POWER_TABLE");
58            None
59        }
60        Ok(i) => {
61            if let Ok(cid) = i.parse() {
62                tracing::info!(
63                    "Using F3 initial power table cid {i} set by FOREST_F3_INITIAL_POWER_TABLE"
64                );
65                Some(cid)
66            } else {
67                tracing::warn!(
68                    "Invalid power table cid {i} set by FOREST_F3_INITIAL_POWER_TABLE, fallback to chain config"
69                );
70                chain_config.f3_initial_power_table
71            }
72        }
73        _ => chain_config.f3_initial_power_table,
74    };
75
76    let bootstrap_epoch = std::env::var("FOREST_F3_BOOTSTRAP_EPOCH")
77        .ok()
78        .and_then(|i| i.parse().ok())
79        .inspect(|i| {
80            tracing::info!("Using F3 bootstrap epoch {i} set by FOREST_F3_BOOTSTRAP_EPOCH")
81        })
82        .unwrap_or(chain_config.f3_bootstrap_epoch);
83
84    F3Options {
85        chain_finality,
86        bootstrap_epoch,
87        initial_power_table,
88    }
89}
90
91#[allow(unused_variables)]
92pub fn run_f3_sidecar_if_enabled(
93    chain_config: &ChainConfig,
94    rpc_endpoint: String,
95    jwt: String,
96    f3_rpc_endpoint: String,
97    initial_power_table: String,
98    bootstrap_epoch: i64,
99    finality: i64,
100    f3_root: String,
101) {
102    if is_sidecar_ffi_enabled(chain_config) {
103        #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))]
104        {
105            tracing::info!("Starting F3 sidecar service ...");
106            GoF3NodeImpl::run(
107                rpc_endpoint,
108                jwt,
109                f3_rpc_endpoint,
110                initial_power_table,
111                bootstrap_epoch,
112                finality,
113                f3_root,
114            );
115        }
116    }
117}
118
119#[allow(unused_variables)]
120pub fn import_f3_snapshot(
121    chain_config: &ChainConfig,
122    rpc_endpoint: String,
123    f3_root: String,
124    snapshot: String,
125) -> anyhow::Result<()> {
126    if is_sidecar_ffi_enabled(chain_config) {
127        #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))]
128        {
129            let sw = std::time::Instant::now();
130            tracing::info!("Importing F3 snapshot ...");
131            let err = GoF3NodeImpl::import_snap(rpc_endpoint, f3_root, snapshot);
132            if !err.is_empty() {
133                anyhow::bail!("{err}");
134            }
135            tracing::info!(
136                "Imported F3 snapshot, took {}",
137                humantime::format_duration(sw.elapsed())
138            );
139        }
140    } else {
141        tracing::warn!("F3 sidecar is disabled, skip importing the F3 snapshot");
142    }
143    Ok(())
144}
145
146/// Whether F3 sidecar via FFI is enabled.
147pub fn is_sidecar_ffi_enabled(chain_config: &ChainConfig) -> bool {
148    // Respect the environment variable when set, and fallback to chain config when not set.
149    let enabled =
150        is_env_set_and_truthy("FOREST_F3_SIDECAR_FFI_ENABLED").unwrap_or(chain_config.f3_enabled);
151    cfg_if::cfg_if! {
152        if #[cfg(all(f3sidecar, not(feature = "no-f3-sidecar")))] {
153            enabled
154        }
155        else {
156            if enabled {
157                tracing::info!("Failed to enable F3 sidecar, the Forest binary is not compiled with f3-sidecar Go lib");
158            }
159            false
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_get_f3_sidecar_params() {
170        let chain_config = ChainConfig::calibnet();
171        // No environment variable overrides
172        assert_eq!(
173            get_f3_sidecar_params(&chain_config),
174            F3Options {
175                chain_finality: chain_config.policy.chain_finality,
176                bootstrap_epoch: chain_config.f3_bootstrap_epoch,
177                initial_power_table: chain_config.f3_initial_power_table,
178            }
179        );
180
181        unsafe {
182            std::env::set_var("FOREST_F3_FINALITY", "100");
183            // A random CID
184            std::env::set_var(
185                "FOREST_F3_INITIAL_POWER_TABLE",
186                "bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i",
187            );
188            std::env::set_var("FOREST_F3_BOOTSTRAP_EPOCH", "100");
189        }
190        assert_eq!(
191            get_f3_sidecar_params(&chain_config),
192            F3Options {
193                chain_finality: 100,
194                bootstrap_epoch: 100,
195                initial_power_table: Some(
196                    "bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i"
197                        .parse()
198                        .unwrap()
199                ),
200            }
201        );
202        // Unset initial power table
203        unsafe { std::env::set_var("FOREST_F3_INITIAL_POWER_TABLE", "") };
204        assert_eq!(
205            get_f3_sidecar_params(&chain_config),
206            F3Options {
207                chain_finality: 100,
208                bootstrap_epoch: 100,
209                initial_power_table: None,
210            }
211        );
212    }
213}