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