1use crate::auth::{ADMIN, create_token, generate_priv_key};
5use crate::chain::ChainStore;
6use crate::cli_shared::chain_path;
7use crate::cli_shared::cli::CliOpts;
8use crate::daemon::asyncify;
9use crate::daemon::bundle::load_actor_bundles;
10use crate::daemon::db_util::load_all_forest_cars_with_cleanup;
11use crate::db::car::ManyCar;
12use crate::db::db_engine::db_root;
13use crate::db::parity_db::{GarbageCollectableParityDb, ParityDb};
14use crate::db::{CAR_DB_DIR_NAME, DummyStore, EthMappingsStore};
15use crate::genesis::read_genesis_header;
16use crate::libp2p::{Keypair, PeerId};
17use crate::networks::ChainConfig;
18use crate::rpc::sync::SnapshotProgressTracker;
19use crate::shim::address::CurrentNetwork;
20use crate::state_manager::StateManager;
21use crate::{
22 Config, ENCRYPTED_KEYSTORE_NAME, FOREST_KEYSTORE_PHRASE_ENV, JWT_IDENTIFIER, KeyStore,
23 KeyStoreConfig,
24};
25use anyhow::Context;
26use dialoguer::console::Term;
27use fvm_shared4::address::Network;
28use parking_lot::RwLock;
29use std::cell::RefCell;
30use std::path::PathBuf;
31use std::sync::Arc;
32use tracing::{info, warn};
33
34pub struct AppContext {
35 pub net_keypair: Keypair,
36 pub p2p_peer_id: PeerId,
37 pub db: Arc<DbType>,
38 pub db_meta_data: DbMetadata,
39 pub state_manager: Arc<StateManager<DbType>>,
40 pub keystore: Arc<RwLock<KeyStore>>,
41 pub admin_jwt: String,
42 pub snapshot_progress_tracker: SnapshotProgressTracker,
43 pub temp_dir: std::path::PathBuf,
44}
45
46impl AppContext {
47 pub async fn init(opts: &CliOpts, cfg: &Config) -> anyhow::Result<AppContext> {
48 let chain_cfg = get_chain_config_and_set_network(cfg);
49 let (net_keypair, p2p_peer_id) = get_or_create_p2p_keypair_and_peer_id(cfg)?;
50 let (db, db_meta_data) = setup_db(opts, cfg).await?;
51 let state_manager = create_state_manager(cfg, &db, &chain_cfg).await?;
52 let (keystore, admin_jwt) = load_or_create_keystore_and_configure_jwt(opts, cfg).await?;
53 let snapshot_progress_tracker = SnapshotProgressTracker::default();
54 let temp_dir = chain_path(cfg).join("tmp");
55 std::fs::create_dir_all(&temp_dir).context("Failed to create temporary directory")?;
56 Ok(Self {
57 net_keypair,
58 p2p_peer_id,
59 db,
60 db_meta_data,
61 state_manager,
62 keystore,
63 admin_jwt,
64 snapshot_progress_tracker,
65 temp_dir,
66 })
67 }
68
69 pub fn chain_config(&self) -> &Arc<ChainConfig> {
70 self.state_manager.chain_config()
71 }
72
73 pub fn chain_store(&self) -> &Arc<ChainStore<DbType>> {
74 self.state_manager.chain_store()
75 }
76}
77
78fn get_chain_config_and_set_network(config: &Config) -> Arc<ChainConfig> {
79 let chain_config = ChainConfig::from_chain(config.chain());
80 if chain_config.is_testnet() {
81 CurrentNetwork::set_global(Network::Testnet);
82 }
83 Arc::new(ChainConfig {
84 enable_indexer: config.chain_indexer.enable_indexer,
85 default_max_fee: config.fee.max_fee.clone(),
86 ..chain_config
87 })
88}
89
90fn get_or_create_p2p_keypair_and_peer_id(config: &Config) -> anyhow::Result<(Keypair, PeerId)> {
91 let path = config.client.data_dir.join("libp2p");
92 let keypair = crate::libp2p::keypair::get_or_create_keypair(&path)?;
93 let peer_id = keypair.public().to_peer_id();
94 Ok((keypair, peer_id))
95}
96
97async fn load_or_create_keystore(config: &Config) -> anyhow::Result<KeyStore> {
102 use std::env::VarError;
103
104 let passphrase_from_env = std::env::var(FOREST_KEYSTORE_PHRASE_ENV);
105 let require_encryption = config.client.encrypt_keystore;
106 let keystore_already_exists = config
107 .client
108 .data_dir
109 .join(ENCRYPTED_KEYSTORE_NAME)
110 .is_dir();
111
112 match (require_encryption, passphrase_from_env) {
113 (false, maybe_passphrase) => {
115 warn!("Forest has encryption disabled");
116 if let Ok(_) | Err(VarError::NotUnicode(_)) = maybe_passphrase {
117 warn!(
118 "Ignoring passphrase provided in {} - encryption is disabled",
119 FOREST_KEYSTORE_PHRASE_ENV
120 )
121 }
122 KeyStore::new(KeyStoreConfig::Persistent(config.client.data_dir.clone()))
123 .map_err(anyhow::Error::new)
124 }
125
126 (true, Ok(passphrase)) => KeyStore::new(KeyStoreConfig::Encrypted(
128 config.client.data_dir.clone(),
129 passphrase,
130 ))
131 .map_err(anyhow::Error::new),
132
133 (true, Err(error)) => {
135 if let VarError::NotUnicode(_) = error {
138 warn!(
140 "Ignoring passphrase provided in {} - it's not utf-8",
141 FOREST_KEYSTORE_PHRASE_ENV
142 )
143 }
144
145 let data_dir = config.client.data_dir.clone();
146
147 match keystore_already_exists {
148 true => asyncify(move || input_password_to_load_encrypted_keystore(data_dir))
149 .await
150 .context("Couldn't load keystore"),
151 false => {
152 let password =
153 asyncify(|| create_password("Create a password for Forest's keystore"))
154 .await?;
155 KeyStore::new(KeyStoreConfig::Encrypted(data_dir, password))
156 .context("Couldn't create keystore")
157 }
158 }
159 }
160 }
161}
162
163async fn load_or_create_keystore_and_configure_jwt(
164 opts: &CliOpts,
165 config: &Config,
166) -> anyhow::Result<(Arc<RwLock<KeyStore>>, String)> {
167 let mut keystore = load_or_create_keystore(config).await?;
168 if keystore.get(JWT_IDENTIFIER).is_err() {
169 keystore.put(JWT_IDENTIFIER, generate_priv_key())?;
170 }
171 let admin_jwt = handle_admin_token(opts, config, &keystore)?;
172 let keystore = Arc::new(RwLock::new(keystore));
173 Ok((keystore, admin_jwt))
174}
175
176fn maybe_migrate_db(config: &Config) {
177 let db_migration = crate::db::migration::DbMigration::new(config);
180 if let Err(e) = db_migration.migrate() {
181 warn!("Failed to migrate database: {e:#}");
182 }
183}
184
185pub type DbType = ManyCar<Arc<GarbageCollectableParityDb>>;
186
187pub(crate) struct DbMetadata {
188 db_root_dir: PathBuf,
189 forest_car_db_dir: PathBuf,
190}
191
192impl DbMetadata {
193 pub(crate) fn get_root_dir(&self) -> PathBuf {
194 self.db_root_dir.clone()
195 }
196
197 pub(crate) fn get_forest_car_db_dir(&self) -> PathBuf {
198 self.forest_car_db_dir.clone()
199 }
200}
201
202async fn setup_db(opts: &CliOpts, config: &Config) -> anyhow::Result<(Arc<DbType>, DbMetadata)> {
208 maybe_migrate_db(config);
209 let chain_data_path = chain_path(config);
210 let db_root_dir = db_root(&chain_data_path)?;
211 let db_writer = Arc::new(GarbageCollectableParityDb::new(ParityDb::to_options(
212 db_root_dir.clone(),
213 config.db_config(),
214 ))?);
215 let db = Arc::new(ManyCar::new(db_writer.clone()));
216 let forest_car_db_dir = db_root_dir.join(CAR_DB_DIR_NAME);
217 load_all_forest_cars_with_cleanup(&db, &forest_car_db_dir)?;
218 if config.client.load_actors && !opts.stateless {
219 load_actor_bundles(&db, config.chain()).await?;
220 }
221 Ok((
222 db,
223 DbMetadata {
224 db_root_dir,
225 forest_car_db_dir,
226 },
227 ))
228}
229
230async fn create_state_manager(
231 config: &Config,
232 db: &Arc<DbType>,
233 chain_config: &Arc<ChainConfig>,
234) -> anyhow::Result<Arc<StateManager<DbType>>> {
235 let genesis_header = read_genesis_header(
239 config.client.genesis_file.as_deref(),
240 chain_config.genesis_bytes(db).await?.as_deref(),
241 db,
242 )
243 .await?;
244
245 let eth_mappings: Arc<dyn EthMappingsStore + Sync + Send> =
246 if config.chain_indexer.enable_indexer {
247 db.writer().clone()
248 } else {
249 Arc::new(DummyStore {})
250 };
251 let chain_store = Arc::new(ChainStore::new(
252 Arc::clone(db),
253 Arc::new(db.clone()),
254 eth_mappings,
255 chain_config.clone(),
256 genesis_header.clone(),
257 )?);
258
259 let state_manager = Arc::new(StateManager::new(Arc::clone(&chain_store))?);
261
262 Ok(state_manager)
263}
264
265fn input_password_to_load_encrypted_keystore(data_dir: PathBuf) -> dialoguer::Result<KeyStore> {
269 let keystore = RefCell::new(None);
270 let term = Term::stderr();
271
272 if !term.is_term() {
276 return Err(std::io::Error::new(
277 std::io::ErrorKind::NotConnected,
278 "cannot read password from non-terminal",
279 )
280 .into());
281 }
282
283 dialoguer::Password::new()
284 .with_prompt("Enter the password for Forest's keystore")
285 .allow_empty_password(true) .validate_with(|input: &String| {
287 KeyStore::new(KeyStoreConfig::Encrypted(data_dir.clone(), input.clone()))
288 .map(|created| *keystore.borrow_mut() = Some(created))
289 .context(
290 "Error: couldn't load keystore with this password. Try again or press Ctrl+C to abort.",
291 )
292 })
293 .interact_on(&term)?;
294
295 Ok(keystore
296 .into_inner()
297 .expect("validation succeeded, so keystore must be emplaced"))
298}
299
300fn create_password(prompt: &str) -> dialoguer::Result<String> {
304 let term = Term::stderr();
305
306 if !term.is_term() {
310 return Err(std::io::Error::new(
311 std::io::ErrorKind::NotConnected,
312 "cannot read password from non-terminal",
313 )
314 .into());
315 }
316 dialoguer::Password::new()
317 .with_prompt(prompt)
318 .allow_empty_password(false)
319 .with_confirmation(
320 "Confirm password",
321 "Error: the passwords do not match. Try again or press Ctrl+C to abort.",
322 )
323 .interact_on(&term)
324}
325
326fn handle_admin_token(
329 opts: &CliOpts,
330 config: &Config,
331 keystore: &KeyStore,
332) -> anyhow::Result<String> {
333 let ki = keystore.get(JWT_IDENTIFIER)?;
334 let token_exp = chrono::Duration::days(365 * 100);
338 let token = create_token(
339 ADMIN.iter().map(ToString::to_string).collect(),
340 ki.private_key(),
341 token_exp,
342 )?;
343 let default_token_path = config.client.default_rpc_token_path();
344 if let Err(e) =
345 crate::utils::io::write_new_sensitive_file(token.as_bytes(), &default_token_path)
346 {
347 tracing::warn!("Failed to save the default admin token file: {e}");
348 } else {
349 info!("Admin token is saved to {}", default_token_path.display());
350 }
351 if let Some(path) = opts.save_token.as_ref() {
352 if let Some(dir) = path.parent()
353 && !dir.is_dir()
354 {
355 std::fs::create_dir_all(dir).with_context(|| {
356 format!(
357 "Failed to create `--save-token` directory {}",
358 dir.display()
359 )
360 })?;
361 }
362 std::fs::write(path, &token)
363 .with_context(|| format!("Failed to save admin token to {}", path.display()))?;
364 info!("Admin token is saved to {}", path.display());
365 }
366
367 Ok(token)
368}