1#![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 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
149pub fn is_sidecar_ffi_enabled(chain_config: &ChainConfig) -> bool {
151 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 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 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 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}