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