1#![deny(clippy::pedantic)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::cast_possible_wrap)]
4#![allow(clippy::cast_precision_loss)]
5#![allow(clippy::cast_sign_loss)]
6#![allow(clippy::doc_markdown)]
7#![allow(clippy::missing_errors_doc)]
8#![allow(clippy::missing_panics_doc)]
9#![allow(clippy::module_name_repetitions)]
10#![allow(clippy::must_use_candidate)]
11#![allow(clippy::needless_lifetimes)]
12#![allow(clippy::ref_option)]
13#![allow(clippy::return_self_not_must_use)]
14#![allow(clippy::similar_names)]
15#![allow(clippy::too_many_lines)]
16#![allow(clippy::needless_pass_by_value)]
17#![allow(clippy::manual_let_else)]
18#![allow(clippy::match_wildcard_for_single_variants)]
19#![allow(clippy::trivially_copy_pass_by_ref)]
20
21extern crate fedimint_core;
24mod db;
25
26use std::fs;
27use std::path::{Path, PathBuf};
28
29use config::io::{read_server_config, PLAINTEXT_PASSWORD};
30use config::ServerConfig;
31use fedimint_aead::random_salt;
32use fedimint_core::db::{Database, DatabaseTransaction, IDatabaseTransactionOpsCoreTyped as _};
33use fedimint_core::epoch::ConsensusItem;
34use fedimint_core::task::TaskGroup;
35use fedimint_core::util::write_new;
36use fedimint_logging::{LOG_CONSENSUS, LOG_CORE};
37pub use fedimint_server_core as core;
38use fedimint_server_core::ServerModuleInitRegistry;
39use net::api::ApiSecrets;
40use tracing::{info, warn};
41
42use crate::config::api::{ConfigGenApi, ConfigGenSettings};
43use crate::config::io::{write_server_config, SALT_FILE};
44use crate::db::{ServerInfo, ServerInfoKey};
45use crate::metrics::initialize_gauge_metrics;
46use crate::net::api::announcement::start_api_announcement_service;
47use crate::net::api::RpcHandlerCtx;
48
49pub mod envs;
50pub mod metrics;
51
52pub mod consensus;
54
55pub mod net;
57
58pub mod config;
60
61pub async fn run(
62 data_dir: PathBuf,
63 force_api_secrets: ApiSecrets,
64 settings: ConfigGenSettings,
65 db: Database,
66 code_version_str: String,
67 module_init_registry: &ServerModuleInitRegistry,
68 task_group: TaskGroup,
69) -> anyhow::Result<()> {
70 let cfg = match get_config(&data_dir)? {
71 Some(cfg) => cfg,
72 None => {
73 run_config_gen(
74 data_dir.clone(),
75 settings.clone(),
76 db.clone(),
77 code_version_str.clone(),
78 task_group.make_subgroup(),
79 force_api_secrets.clone(),
80 )
81 .await?
82 }
83 };
84
85 let decoders = module_init_registry.decoders_strict(
86 cfg.consensus
87 .modules
88 .iter()
89 .map(|(id, config)| (*id, &config.kind)),
90 )?;
91
92 let db = db.with_decoders(decoders);
93
94 initialize_gauge_metrics(&db).await;
95
96 start_api_announcement_service(&db, &task_group, &cfg, force_api_secrets.get_active()).await;
97
98 consensus::run(
99 settings.p2p_bind,
100 settings.api_bind,
101 cfg,
102 db,
103 module_init_registry.clone(),
104 &task_group,
105 force_api_secrets,
106 data_dir,
107 code_version_str,
108 )
109 .await?;
110
111 info!(target: LOG_CONSENSUS, "Shutting down tasks");
112
113 task_group.shutdown();
114
115 Ok(())
116}
117
118async fn update_server_info_version_dbtx(
119 dbtx: &mut DatabaseTransaction<'_>,
120 code_version_str: &str,
121) {
122 let mut server_info = dbtx.get_value(&ServerInfoKey).await.unwrap_or(ServerInfo {
123 init_version: code_version_str.to_string(),
124 last_version: code_version_str.to_string(),
125 });
126 server_info.last_version = code_version_str.to_string();
127 dbtx.insert_entry(&ServerInfoKey, &server_info).await;
128}
129
130pub fn get_config(data_dir: &Path) -> anyhow::Result<Option<ServerConfig>> {
131 let path = data_dir.join(PLAINTEXT_PASSWORD);
133 if let Ok(password_untrimmed) = fs::read_to_string(&path) {
134 let password = password_untrimmed.trim_matches('\n');
138 let password_fully_trimmed = password.trim();
140 if password_fully_trimmed != password {
141 warn!(
142 target: LOG_CORE,
143 path = %path.display(),
144 "Password in the password file contains leading/trailing whitespaces. This will an error in the future."
145 );
146 }
147 return Ok(Some(read_server_config(password, data_dir)?));
148 }
149
150 Ok(None)
151}
152
153pub async fn run_config_gen(
154 data_dir: PathBuf,
155 settings: ConfigGenSettings,
156 db: Database,
157 code_version_str: String,
158 task_group: TaskGroup,
159 force_api_secrets: ApiSecrets,
160) -> anyhow::Result<ServerConfig> {
161 info!(target: LOG_CONSENSUS, "Starting config gen");
162
163 initialize_gauge_metrics(&db).await;
164
165 let (cfg_sender, mut cfg_receiver) = tokio::sync::mpsc::channel(1);
166
167 let config_gen = ConfigGenApi::new(
168 settings.p2p_bind,
169 settings.clone(),
170 db.clone(),
171 cfg_sender,
172 &task_group,
173 code_version_str.clone(),
174 force_api_secrets.get_active(),
175 );
176
177 let mut rpc_module = RpcHandlerCtx::new_module(config_gen);
178
179 net::api::attach_endpoints(&mut rpc_module, config::api::server_endpoints(), None);
180
181 let api_handler = net::api::spawn(
182 "config-gen",
183 settings.api_bind,
184 rpc_module,
185 10,
186 force_api_secrets.clone(),
187 )
188 .await;
189
190 let cfg = cfg_receiver.recv().await.expect("should not close");
191
192 api_handler
193 .stop()
194 .expect("Config api should still be running");
195
196 api_handler.stopped().await;
197
198 write_new(data_dir.join(PLAINTEXT_PASSWORD), &cfg.private.api_auth.0)?;
200 write_new(data_dir.join(SALT_FILE), random_salt())?;
201 write_server_config(
202 &cfg,
203 &data_dir,
204 &cfg.private.api_auth.0,
205 &settings.registry,
206 force_api_secrets.get_active(),
207 )?;
208
209 Ok(cfg)
210}