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