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