1use std::collections::BTreeMap;
2use std::net::SocketAddr;
3use std::sync::Arc;
4use std::time::Duration;
5
6use async_trait::async_trait;
7use bitcoin::hashes::sha256;
8use bitcoincore_rpc::RpcApi;
9use fedimint_api_client::api::{DynGlobalApi, StatusResponse};
10use fedimint_bitcoind::create_bitcoind;
11use fedimint_core::admin_client::{
12 ConfigGenConnectionsRequest, ConfigGenParamsConsensus, ConfigGenParamsRequest,
13 ConfigGenParamsResponse, PeerServerParams, ServerStatus,
14};
15use fedimint_core::config::{
16 ConfigGenModuleParams, ServerModuleConfigGenParamsRegistry, ServerModuleInitRegistry,
17};
18use fedimint_core::core::ModuleInstanceId;
19use fedimint_core::db::Database;
20use fedimint_core::encoding::Encodable;
21use fedimint_core::endpoint_constants::{
22 ADD_CONFIG_GEN_PEER_ENDPOINT, AUTH_ENDPOINT, CHECK_BITCOIN_STATUS_ENDPOINT,
23 CONFIG_GEN_PEERS_ENDPOINT, CONSENSUS_CONFIG_GEN_PARAMS_ENDPOINT,
24 DEFAULT_CONFIG_GEN_PARAMS_ENDPOINT, RESTART_FEDERATION_SETUP_ENDPOINT, RUN_DKG_ENDPOINT,
25 SET_CONFIG_GEN_CONNECTIONS_ENDPOINT, SET_CONFIG_GEN_PARAMS_ENDPOINT, SET_PASSWORD_ENDPOINT,
26 START_CONSENSUS_ENDPOINT, STATUS_ENDPOINT, VERIFIED_CONFIGS_ENDPOINT,
27 VERIFY_CONFIG_HASH_ENDPOINT,
28};
29use fedimint_core::envs::BitcoinRpcConfig;
30use fedimint_core::module::{
31 api_endpoint, ApiAuth, ApiEndpoint, ApiEndpointContext, ApiError, ApiRequestErased, ApiVersion,
32};
33use fedimint_core::task::{block_in_place, sleep, TaskGroup};
34use fedimint_core::util::SafeUrl;
35use fedimint_core::PeerId;
36use itertools::Itertools;
37use serde::{Deserialize, Serialize};
38use tokio::sync::mpsc::Sender;
39use tokio::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
40use tokio::time::Instant;
41use tokio_rustls::rustls;
42use tracing::{error, info};
43
44use crate::config::{gen_cert_and_key, ConfigGenParams, ServerConfig};
45use crate::envs::FM_PEER_ID_SORT_BY_URL_ENV;
46use crate::net::api::{check_auth, ApiResult, HasApiContext};
47use crate::net::peers::DelayCalculator;
48
49#[derive(Clone)]
51pub struct ConfigGenApi {
52 state: Arc<Mutex<ConfigGenState>>,
54 db: Database,
56 config_generated_tx: Sender<ServerConfig>,
58 task_group: TaskGroup,
60 code_version_str: String,
62 api_secret: Option<String>,
64 p2p_bind_addr: SocketAddr,
65 bitcoin_status_cache: Arc<RwLock<Option<(Instant, BitcoinRpcConnectionStatus)>>>,
66 bitcoin_status_cache_duration: Duration,
67}
68
69impl ConfigGenApi {
70 pub fn new(
71 p2p_bind_addr: SocketAddr,
72 settings: ConfigGenSettings,
73 db: Database,
74 config_generated_tx: Sender<ServerConfig>,
75 task_group: &TaskGroup,
76 code_version_str: String,
77 api_secret: Option<String>,
78 ) -> Self {
79 let config_gen_api = Self {
80 state: Arc::new(Mutex::new(ConfigGenState::new(settings))),
81 db,
82 config_generated_tx,
83 task_group: task_group.clone(),
84 code_version_str,
85 api_secret,
86 p2p_bind_addr,
87 bitcoin_status_cache: Arc::new(RwLock::new(None)),
88 bitcoin_status_cache_duration: Duration::from_secs(60),
89 };
90 info!(target: fedimint_logging::LOG_NET_PEER_DKG, "Created new config gen Api");
91 config_gen_api
92 }
93
94 pub async fn set_password(&self, auth: ApiAuth) -> ApiResult<()> {
96 let mut state = self.require_status(ServerStatus::AwaitingPassword).await?;
97 let auth_trimmed = auth.0.trim();
98 if auth_trimmed != auth.0 {
99 return Err(ApiError::bad_request(
100 "Password contains leading/trailing whitespace".to_string(),
101 ));
102 }
103 state.auth = Some(auth);
104 state.status = ServerStatus::SharingConfigGenParams;
105 info!(
106 target: fedimint_logging::LOG_NET_PEER_DKG,
107 "Set password for config gen"
108 );
109 Ok(())
110 }
111
112 async fn require_status(&self, status: ServerStatus) -> ApiResult<MutexGuard<ConfigGenState>> {
113 let state = self.state.lock().await;
114 if state.status != status {
115 return Self::bad_request(&format!("Expected to be in {status:?} state"));
116 }
117 Ok(state)
118 }
119
120 async fn require_any_status(
121 &self,
122 statuses: &[ServerStatus],
123 ) -> ApiResult<MutexGuard<ConfigGenState>> {
124 let state = self.state.lock().await;
125 if !statuses.contains(&state.status) {
126 return Self::bad_request(&format!("Expected to be in one of {statuses:?} states"));
127 }
128 Ok(state)
129 }
130
131 pub async fn set_config_gen_connections(
133 &self,
134 request: ConfigGenConnectionsRequest,
135 ) -> ApiResult<()> {
136 {
137 let mut state = self
138 .require_status(ServerStatus::SharingConfigGenParams)
139 .await?;
140 state.set_request(request)?;
141 }
142 self.update_leader().await?;
143 Ok(())
144 }
145
146 async fn update_leader(&self) -> ApiResult<()> {
148 let state = self.state.lock().await.clone();
149 let local = state.local.clone();
150
151 if let Some(url) = local.and_then(|local| local.leader_api_url) {
152 DynGlobalApi::from_pre_peer_id_admin_endpoint(url, &self.api_secret)
153 .add_config_gen_peer(state.our_peer_info()?)
154 .await
155 .map_err(|_| ApiError::not_found("Unable to connect to the leader".to_string()))?;
156 }
157 Ok(())
158 }
159
160 pub async fn add_config_gen_peer(&self, peer: PeerServerParams) -> ApiResult<()> {
163 let mut state = self.state.lock().await;
164 state.peers.insert(peer.api_url.clone(), peer);
165 info!(target: fedimint_logging::LOG_NET_PEER_DKG, "New peer added to config gen");
166 Ok(())
167 }
168
169 pub async fn config_gen_peers(&self) -> ApiResult<Vec<PeerServerParams>> {
171 let state = self.state.lock().await;
172 Ok(state.get_peer_info().into_values().collect())
173 }
174
175 pub async fn default_config_gen_params(&self) -> ApiResult<ConfigGenParamsRequest> {
177 let state = self.state.lock().await;
178 Ok(state.settings.default_params.clone())
179 }
180
181 pub async fn set_config_gen_params(&self, request: ConfigGenParamsRequest) -> ApiResult<()> {
185 self.consensus_config_gen_params(&request).await?;
186 let mut state = self
187 .require_status(ServerStatus::SharingConfigGenParams)
188 .await?;
189 state.requested_params = Some(request);
190 info!(
191 target: fedimint_logging::LOG_NET_PEER_DKG,
192 "Set params for config gen"
193 );
194 Ok(())
195 }
196
197 async fn get_requested_params(&self) -> ApiResult<ConfigGenParamsRequest> {
198 let state = self.state.lock().await.clone();
199 state.requested_params.ok_or(ApiError::bad_request(
200 "Config params were not set on this guardian".to_string(),
201 ))
202 }
203
204 pub async fn consensus_config_gen_params(
206 &self,
207 request: &ConfigGenParamsRequest,
208 ) -> ApiResult<ConfigGenParamsResponse> {
209 let state = self.state.lock().await.clone();
210 let local = state.local.clone();
211
212 let consensus = match local.and_then(|local| local.leader_api_url) {
213 Some(leader_url) => {
214 let client = DynGlobalApi::from_pre_peer_id_admin_endpoint(
215 leader_url.clone(),
216 &self.api_secret,
217 );
218 let response = client.consensus_config_gen_params().await;
219 response
220 .map_err(|_| ApiError::not_found("Cannot get leader params".to_string()))?
221 .consensus
222 }
223 None => ConfigGenParamsConsensus {
224 peers: state.get_peer_info(),
225 meta: request.meta.clone(),
226 modules: request.modules.clone(),
227 },
228 };
229
230 let params = state.get_config_gen_params(request, consensus.clone())?;
231 Ok(ConfigGenParamsResponse {
232 consensus,
233 our_current_id: params.local.our_id,
234 })
235 }
236
237 pub async fn run_dkg(&self) -> ApiResult<()> {
245 let leader = {
246 let mut state = self
247 .require_status(ServerStatus::SharingConfigGenParams)
248 .await?;
249 state.status = ServerStatus::ReadyForConfigGen;
251 info!(
252 target: fedimint_logging::LOG_NET_PEER_DKG,
253 "Update config gen status to 'Ready for config gen'"
254 );
255 state.local.clone().and_then(|local| {
257 local.leader_api_url.map(|url| {
258 DynGlobalApi::from_pre_peer_id_admin_endpoint(url, &self.api_secret.clone())
259 })
260 })
261 };
262
263 self.update_leader().await?;
264
265 let self_clone = self.clone();
266 let sub_group = self.task_group.make_subgroup();
267 let p2p_bind_addr = self.p2p_bind_addr;
268 sub_group.spawn("run dkg", move |_handle| async move {
269 if let Some(client) = leader {
271 loop {
272 let status = client.status().await.map_err(|_| {
273 ApiError::not_found("Unable to connect to the leader".to_string())
274 })?;
275 if status.server == ServerStatus::ReadyForConfigGen {
276 break;
277 }
278 sleep(Duration::from_millis(100)).await;
279 }
280 };
281
282 let request = self_clone.get_requested_params().await?;
284 let response = self_clone.consensus_config_gen_params(&request).await?;
285 let (params, registry) = {
286 let state: MutexGuard<'_, ConfigGenState> = self_clone
287 .require_status(ServerStatus::ReadyForConfigGen)
288 .await?;
289 let params = state.get_config_gen_params(&request, response.consensus)?;
290 let registry = state.settings.registry.clone();
291 (params, registry)
292 };
293
294 let task_group: TaskGroup = self_clone.task_group.make_subgroup();
296 let config = ServerConfig::distributed_gen(
297 p2p_bind_addr,
298 ¶ms,
299 registry,
300 DelayCalculator::PROD_DEFAULT,
301 &task_group,
302 self_clone.code_version_str.clone(),
303 )
304 .await;
305 task_group
306 .shutdown_join_all(None)
307 .await
308 .expect("shuts down");
309
310 {
311 let mut state = self_clone.state.lock().await;
312 match config {
313 Ok(config) => {
314 state.status = ServerStatus::VerifyingConfigs;
315 state.config = Some(config);
316 info!(
317 target: fedimint_logging::LOG_NET_PEER_DKG,
318 "Set config for config gen"
319 );
320 }
321 Err(e) => {
322 error!(
323 target: fedimint_logging::LOG_NET_PEER_DKG,
324 "DKG failed with {:?}", e
325 );
326 state.status = ServerStatus::ConfigGenFailed;
327 info!(
328 target: fedimint_logging::LOG_NET_PEER_DKG,
329 "Update config gen status to 'Config gen failed'"
330 );
331 }
332 }
333 }
334 self_clone.update_leader().await
335 });
336
337 Ok(())
338 }
339
340 pub async fn verify_config_hash(&self) -> ApiResult<BTreeMap<PeerId, sha256::Hash>> {
345 let expected_status = [
346 ServerStatus::VerifyingConfigs,
347 ServerStatus::VerifiedConfigs,
348 ];
349
350 let state = self.require_any_status(&expected_status).await?;
351
352 let config = state
353 .config
354 .clone()
355 .ok_or(ApiError::bad_request("Missing config".to_string()))?;
356
357 let verification_hashes = config
358 .consensus
359 .api_endpoints
360 .keys()
361 .map(|peer| (*peer, (*peer, config.consensus.clone()).consensus_hash()))
362 .collect();
363
364 Ok(verification_hashes)
365 }
366
367 pub async fn verified_configs(&self) -> ApiResult<()> {
369 {
370 let expected_status = [
371 ServerStatus::VerifyingConfigs,
372 ServerStatus::VerifiedConfigs,
373 ];
374 let mut state = self.require_any_status(&expected_status).await?;
375 if state.status == ServerStatus::VerifiedConfigs {
376 return Ok(());
377 }
378 state.status = ServerStatus::VerifiedConfigs;
379 info!(
380 target: fedimint_logging::LOG_NET_PEER_DKG,
381 "Update config gen status to 'Verified configs'"
382 );
383 }
384
385 self.update_leader().await?;
386 Ok(())
387 }
388
389 pub async fn start_consensus(&self) -> ApiResult<()> {
390 let state = self
391 .require_any_status(&[
392 ServerStatus::VerifyingConfigs,
393 ServerStatus::VerifiedConfigs,
394 ])
395 .await?;
396
397 self.config_generated_tx
398 .send(state.config.clone().expect("Config should exist"))
399 .await
400 .expect("Can send");
401
402 Ok(())
403 }
404
405 pub async fn server_status(&self) -> ServerStatus {
407 self.state.lock().await.status.clone()
408 }
409
410 fn bad_request<T>(msg: &str) -> ApiResult<T> {
411 Err(ApiError::bad_request(msg.to_string()))
412 }
413
414 pub async fn restart_federation_setup(&self) -> ApiResult<()> {
415 let leader = {
416 let expected_status = [
417 ServerStatus::SharingConfigGenParams,
418 ServerStatus::ReadyForConfigGen,
419 ServerStatus::ConfigGenFailed,
420 ServerStatus::VerifyingConfigs,
421 ServerStatus::VerifiedConfigs,
422 ];
423 let mut state = self.require_any_status(&expected_status).await?;
424
425 state.status = ServerStatus::SetupRestarted;
426 info!(
427 target: fedimint_logging::LOG_NET_PEER_DKG,
428 "Update config gen status to 'Setup restarted'"
429 );
430 state.local.clone().and_then(|local| {
432 local
433 .leader_api_url
434 .map(|url| DynGlobalApi::from_pre_peer_id_admin_endpoint(url, &self.api_secret))
435 })
436 };
437
438 self.update_leader().await?;
439
440 let self_clone = self.clone();
443 let sub_group = self.task_group.make_subgroup();
444 sub_group.spawn("restart", |_handle| async move {
445 if let Some(client) = leader {
446 self_clone.await_leader_restart(&client).await?;
447 } else {
448 self_clone.await_peer_restart().await;
449 }
450 {
452 let mut state = self_clone.state.lock().await;
453 state.reset();
454 }
455 self_clone.update_leader().await
456 });
457
458 Ok(())
459 }
460
461 async fn await_leader_restart(&self, client: &DynGlobalApi) -> ApiResult<()> {
463 let mut retries = 0;
464 loop {
465 if let Ok(status) = client.status().await {
466 if status.server == ServerStatus::AwaitingPassword
467 || status.server == ServerStatus::SharingConfigGenParams
468 {
469 break Ok(());
470 }
471 } else {
472 if retries > 3 {
473 return Err(ApiError::not_found(
474 "Unable to connect to the leader".to_string(),
475 ));
476 }
477 retries += 1;
478 }
479 sleep(Duration::from_millis(100)).await;
480 }
481 }
482
483 async fn await_peer_restart(&self) {
485 loop {
486 {
487 let state = self.state.lock().await;
488 let peers = state.peers.clone();
489 if peers
490 .values()
491 .all(|peer| peer.status == Some(ServerStatus::SetupRestarted))
492 {
493 break;
494 }
495 }
496 sleep(Duration::from_millis(100)).await;
497 }
498 }
499
500 pub async fn check_bitcoin_status(&self) -> ApiResult<BitcoinRpcConnectionStatus> {
502 {
504 let cached_status: RwLockReadGuard<'_, Option<(Instant, BitcoinRpcConnectionStatus)>> =
505 self.bitcoin_status_cache.read().await;
506 if let Some((timestamp, status)) = cached_status.as_ref() {
507 if timestamp.elapsed() < self.bitcoin_status_cache_duration {
508 return Ok(*status);
509 }
510 }
511 }
512
513 let bitcoin_rpc_config = BitcoinRpcConfig::get_defaults_from_env_vars().map_err(|e| {
515 ApiError::server_error(format!("Failed to get bitcoin rpc env vars: {e}"))
516 })?;
517 let status = match bitcoin_rpc_config.kind.as_str() {
518 "bitcoind" => check_bitcoind_status(&bitcoin_rpc_config),
519 "esplora" => self.check_esplora_status(&bitcoin_rpc_config).await,
520 _ => Err(ApiError::bad_request(format!(
521 "Unsupported bitcoin rpc kind: {}",
522 bitcoin_rpc_config.kind
523 ))),
524 }?;
525
526 let mut cached_status: RwLockWriteGuard<'_, Option<(Instant, BitcoinRpcConnectionStatus)>> =
528 self.bitcoin_status_cache.write().await;
529 *cached_status = Some((Instant::now(), status));
530
531 Ok(status)
532 }
533
534 async fn check_esplora_status(
535 &self,
536 bitcoin_rpc_config: &BitcoinRpcConfig,
537 ) -> ApiResult<BitcoinRpcConnectionStatus> {
538 let client =
539 create_bitcoind(bitcoin_rpc_config, self.task_group.make_handle()).map_err(|e| {
540 ApiError::server_error(format!("Failed to connect to bitcoin rpc: {e}"))
541 })?;
542 let _network_info = client
545 .get_network()
546 .await
547 .map_err(|e| ApiError::server_error(format!("Failed to get esplora info: {e}")))?;
548 Ok(BitcoinRpcConnectionStatus::Synced)
549 }
550}
551
552fn check_bitcoind_status(
553 bitcoin_rpc_config: &BitcoinRpcConfig,
554) -> ApiResult<BitcoinRpcConnectionStatus> {
555 let (url, auth) = fedimint_bitcoind::bitcoincore::from_url_to_url_auth(&bitcoin_rpc_config.url)
556 .map_err(|e| ApiError::server_error(format!("Failed to parse bitcoin rpc url: {e}")))?;
557 let client = bitcoincore_rpc::Client::new(&url, auth)
558 .map_err(|e| ApiError::server_error(format!("Failed to connect to bitcoin rpc: {e}")))?;
559 let blockchain_info = block_in_place(|| client.get_blockchain_info())
560 .map_err(|e| ApiError::server_error(format!("Failed to get blockchain info: {e}")))?;
561
562 if blockchain_info.initial_block_download {
563 return Ok(BitcoinRpcConnectionStatus::Syncing(
564 blockchain_info.verification_progress,
565 ));
566 }
567
568 Ok(BitcoinRpcConnectionStatus::Synced)
569}
570
571#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
572pub enum BitcoinRpcConnectionStatus {
573 Synced,
574 Syncing(f64),
575}
576
577#[derive(Debug, Clone)]
579pub struct ConfigGenParamsLocal {
580 pub our_id: PeerId,
582 pub our_private_key: rustls::PrivateKey,
584 pub api_auth: ApiAuth,
586 pub p2p_bind: SocketAddr,
588 pub api_bind: SocketAddr,
590 pub max_connections: u32,
592}
593
594#[derive(Debug, Clone)]
596pub struct ConfigGenSettings {
597 pub download_token_limit: Option<u64>,
599 pub p2p_bind: SocketAddr,
601 pub api_bind: SocketAddr,
603 pub p2p_url: SafeUrl,
605 pub api_url: SafeUrl,
607 pub default_params: ConfigGenParamsRequest,
609 pub max_connections: u32,
611 pub registry: ServerModuleInitRegistry,
613}
614
615#[derive(Debug, Clone)]
617pub struct ConfigGenState {
618 settings: ConfigGenSettings,
620 auth: Option<ApiAuth>,
622 local: Option<ConfigGenLocalConnection>,
624 peers: BTreeMap<SafeUrl, PeerServerParams>,
627 requested_params: Option<ConfigGenParamsRequest>,
629 status: ServerStatus,
631 config: Option<ServerConfig>,
633}
634
635#[derive(Debug, Clone)]
637struct ConfigGenLocalConnection {
638 tls_private: rustls::PrivateKey,
640 tls_cert: rustls::Certificate,
642 our_name: String,
644 leader_api_url: Option<SafeUrl>,
647}
648
649impl ConfigGenState {
650 fn new(settings: ConfigGenSettings) -> Self {
651 Self {
652 settings,
653 auth: None,
654 local: None,
655 peers: BTreeMap::new(),
656 requested_params: None,
657 status: ServerStatus::AwaitingPassword,
658 config: None,
659 }
660 }
661
662 fn set_request(&mut self, request: ConfigGenConnectionsRequest) -> ApiResult<()> {
663 let (tls_cert, tls_private) = gen_cert_and_key(&request.our_name)
664 .map_err(|_| ApiError::server_error("Unable to generate TLS keys".to_string()))?;
665 self.local = Some(ConfigGenLocalConnection {
666 tls_private,
667 tls_cert,
668 our_name: request.our_name,
669 leader_api_url: request.leader_api_url,
670 });
671 info!(
672 target: fedimint_logging::LOG_NET_PEER_DKG,
673 "Set local connection for config gen"
674 );
675 Ok(())
676 }
677
678 fn local_connection(&self) -> ApiResult<ConfigGenLocalConnection> {
679 self.local.clone().ok_or(ApiError::bad_request(
680 "Our connection info not set yet".to_string(),
681 ))
682 }
683
684 fn auth(&self) -> ApiResult<ApiAuth> {
685 self.auth
686 .clone()
687 .ok_or(ApiError::bad_request("Missing auth".to_string()))
688 }
689
690 fn our_peer_info(&self) -> ApiResult<PeerServerParams> {
691 let local = self.local_connection()?;
692 Ok(PeerServerParams {
693 cert: local.tls_cert.clone(),
694 p2p_url: self.settings.p2p_url.clone(),
695 api_url: self.settings.api_url.clone(),
696 name: local.our_name,
697 status: Some(self.status.clone()),
698 })
699 }
700
701 fn get_peer_info(&self) -> BTreeMap<PeerId, PeerServerParams> {
702 self.peers
703 .values()
704 .cloned()
705 .chain(self.our_peer_info().ok())
706 .sorted_by_cached_key(|peer| {
710 if std::env::var_os(FM_PEER_ID_SORT_BY_URL_ENV).is_some_and(|var| !var.is_empty()) {
713 peer.api_url.to_string()
714 } else {
715 peer.name.to_lowercase()
716 }
717 })
718 .enumerate()
719 .map(|(i, peer)| (PeerId::from(i as u16), peer))
720 .collect()
721 }
722
723 fn get_config_gen_params(
726 &self,
727 request: &ConfigGenParamsRequest,
728 mut consensus: ConfigGenParamsConsensus,
729 ) -> ApiResult<ConfigGenParams> {
730 let local_connection = self.local_connection()?;
731
732 let (our_id, _) = consensus
733 .peers
734 .iter()
735 .find(|(_, param)| local_connection.tls_cert == param.cert)
736 .ok_or(ApiError::bad_request(
737 "Our TLS cert not found among peers".to_string(),
738 ))?;
739
740 let mut combined_params = vec![];
741 let default_params = self.settings.default_params.modules.clone();
742 let local_params = request.modules.clone();
743 let consensus_params = consensus.modules.clone();
744 for (id, kind, default) in default_params.iter_modules() {
746 let consensus = &consensus_params.get(id).unwrap_or(default).consensus;
747 let local = &local_params.get(id).unwrap_or(default).local;
748 let combined = ConfigGenModuleParams::new(local.clone(), consensus.clone());
749 let module = self.settings.registry.get(kind).expect("Module exists");
751 module.validate_params(&combined).map_err(|e| {
752 ApiError::bad_request(format!(
753 "Module {} params invalid: {}",
754 id,
755 itertools::join(e.chain(), ": ")
756 ))
757 })?;
758 combined_params.push((id, kind.clone(), combined));
759 }
760 consensus.modules = ServerModuleConfigGenParamsRegistry::from_iter(combined_params);
761
762 let local = ConfigGenParamsLocal {
763 our_id: *our_id,
764 our_private_key: local_connection.tls_private,
765 api_auth: self.auth()?,
766 p2p_bind: self.settings.p2p_bind,
767 api_bind: self.settings.api_bind,
768 max_connections: self.settings.max_connections,
769 };
770
771 Ok(ConfigGenParams { local, consensus })
772 }
773
774 fn reset(&mut self) {
775 self.auth = None;
776 self.local = None;
777 self.peers = BTreeMap::new();
778 self.requested_params = None;
779 self.status = ServerStatus::AwaitingPassword;
780 self.config = None;
781
782 info!(
783 target: fedimint_logging::LOG_NET_PEER_DKG,
784 "Reset config gen state"
785 );
786 }
787}
788
789#[async_trait]
790impl HasApiContext<ConfigGenApi> for ConfigGenApi {
791 async fn context(
792 &self,
793 request: &ApiRequestErased,
794 id: Option<ModuleInstanceId>,
795 ) -> (&ConfigGenApi, ApiEndpointContext<'_>) {
796 let mut db = self.db.clone();
797 let mut dbtx = self.db.begin_transaction().await;
798 if let Some(id) = id {
799 db = self.db.with_prefix_module_id(id).0;
800 dbtx = dbtx.with_prefix_module_id(id).0;
801 }
802 let state = self.state.lock().await;
803 let auth = request.auth.as_ref();
804 let has_auth = match state.auth.clone() {
805 None => true,
807 Some(configured_auth) => Some(&configured_auth) == auth,
808 };
809
810 (
811 self,
812 ApiEndpointContext::new(db, dbtx, has_auth, request.auth.clone()),
813 )
814 }
815}
816
817pub fn server_endpoints() -> Vec<ApiEndpoint<ConfigGenApi>> {
818 vec![
819 api_endpoint! {
820 SET_PASSWORD_ENDPOINT,
821 ApiVersion::new(0, 0),
822 async |config: &ConfigGenApi, context, _v: ()| -> () {
823 match context.request_auth() {
824 None => return Err(ApiError::bad_request("Missing password".to_string())),
825 Some(auth) => config.set_password(auth).await
826 }
827 }
828 },
829 api_endpoint! {
830 SET_CONFIG_GEN_CONNECTIONS_ENDPOINT,
831 ApiVersion::new(0, 0),
832 async |config: &ConfigGenApi, context, server: ConfigGenConnectionsRequest| -> () {
833 check_auth(context)?;
834 config.set_config_gen_connections(server).await
835 }
836 },
837 api_endpoint! {
838 ADD_CONFIG_GEN_PEER_ENDPOINT,
839 ApiVersion::new(0, 0),
840 async |config: &ConfigGenApi, _context, peer: PeerServerParams| -> () {
841 config.add_config_gen_peer(peer).await
843 }
844 },
845 api_endpoint! {
846 CONFIG_GEN_PEERS_ENDPOINT,
847 ApiVersion::new(0, 0),
848 async |config: &ConfigGenApi, _context, _v: ()| -> Vec<PeerServerParams> {
849 config.config_gen_peers().await
850 }
851 },
852 api_endpoint! {
853 DEFAULT_CONFIG_GEN_PARAMS_ENDPOINT,
854 ApiVersion::new(0, 0),
855 async |config: &ConfigGenApi, context, _v: ()| -> ConfigGenParamsRequest {
856 check_auth(context)?;
857 config.default_config_gen_params().await
858 }
859 },
860 api_endpoint! {
861 SET_CONFIG_GEN_PARAMS_ENDPOINT,
862 ApiVersion::new(0, 0),
863 async |config: &ConfigGenApi, context, params: ConfigGenParamsRequest| -> () {
864 check_auth(context)?;
865 config.set_config_gen_params(params).await
866 }
867 },
868 api_endpoint! {
869 CONSENSUS_CONFIG_GEN_PARAMS_ENDPOINT,
870 ApiVersion::new(0, 0),
871 async |config: &ConfigGenApi, _context, _v: ()| -> ConfigGenParamsResponse {
872 let request = config.get_requested_params().await?;
873 config.consensus_config_gen_params(&request).await
874 }
875 },
876 api_endpoint! {
877 RUN_DKG_ENDPOINT,
878 ApiVersion::new(0, 0),
879 async |config: &ConfigGenApi, context, _v: ()| -> () {
880 check_auth(context)?;
881 config.run_dkg().await
882 }
883 },
884 api_endpoint! {
885 VERIFY_CONFIG_HASH_ENDPOINT,
886 ApiVersion::new(0, 0),
887 async |config: &ConfigGenApi, context, _v: ()| -> BTreeMap<PeerId, sha256::Hash> {
888 check_auth(context)?;
889 config.verify_config_hash().await
890 }
891 },
892 api_endpoint! {
893 VERIFIED_CONFIGS_ENDPOINT,
894 ApiVersion::new(0, 0),
895 async |config: &ConfigGenApi, context, _v: ()| -> () {
896 check_auth(context)?;
897 config.verified_configs().await
898 }
899 },
900 api_endpoint! {
901 START_CONSENSUS_ENDPOINT,
902 ApiVersion::new(0, 0),
903 async |config: &ConfigGenApi, context, _v: ()| -> () {
904 check_auth(context)?;
905 config.start_consensus().await
906 }
907 },
908 api_endpoint! {
909 STATUS_ENDPOINT,
910 ApiVersion::new(0, 0),
911 async |config: &ConfigGenApi, _context, _v: ()| -> StatusResponse {
912 let server = config.server_status().await;
913 Ok(StatusResponse {
914 server,
915 federation: None
916 })
917 }
918 },
919 api_endpoint! {
920 AUTH_ENDPOINT,
921 ApiVersion::new(0, 0),
922 async |_config: &ConfigGenApi, context, _v: ()| -> () {
923 check_auth(context)?;
924 Ok(())
925 }
926 },
927 api_endpoint! {
928 RESTART_FEDERATION_SETUP_ENDPOINT,
929 ApiVersion::new(0, 0),
930 async |config: &ConfigGenApi, context, _v: ()| -> () {
931 check_auth(context)?;
932 config.restart_federation_setup().await
933 }
934 },
935 api_endpoint! {
936 CHECK_BITCOIN_STATUS_ENDPOINT,
937 ApiVersion::new(0, 0),
938 async |config: &ConfigGenApi, context, _v: ()| -> BitcoinRpcConnectionStatus {
939 check_auth(context)?;
940 config.check_bitcoin_status().await
941 }
942 },
943 ]
944}
945
946#[cfg(test)]
947mod tests {
948
949 use std::collections::{BTreeMap, BTreeSet, HashSet};
950 use std::fs;
951 use std::path::{Path, PathBuf};
952 use std::sync::Arc;
953 use std::time::Duration;
954
955 use fedimint_api_client::api::{DynGlobalApi, FederationResult, StatusResponse};
956 use fedimint_core::admin_client::{ConfigGenParamsRequest, ServerStatus};
957 use fedimint_core::config::{ServerModuleConfigGenParamsRegistry, ServerModuleInitRegistry};
958 use fedimint_core::db::mem_impl::MemDatabase;
959 use fedimint_core::db::IRawDatabaseExt;
960 use fedimint_core::module::ApiAuth;
961 use fedimint_core::runtime::spawn;
962 use fedimint_core::task::{sleep, TaskGroup};
963 use fedimint_core::util::SafeUrl;
964 use fedimint_core::Amount;
965 use fedimint_dummy_common::config::{
966 DummyConfig, DummyGenParams, DummyGenParamsConsensus, DummyGenParamsLocal,
967 };
968 use fedimint_dummy_server::DummyInit;
969 use fedimint_logging::TracingSetup;
970 use fedimint_portalloc::port_alloc;
971 use fedimint_testing_core::test_dir;
972 use futures::future::join_all;
973 use itertools::Itertools;
974 use tracing::info;
975
976 use crate::config::api::{ConfigGenConnectionsRequest, ConfigGenSettings};
977 use crate::config::io::{read_server_config, PLAINTEXT_PASSWORD};
978 use crate::config::{DynServerModuleInit, ServerConfig, DEFAULT_MAX_CLIENT_CONNECTIONS};
979 use crate::fedimint_core::module::ServerModuleInit;
980 use crate::net::api::ApiSecrets;
981
982 struct TestConfigApi {
984 client: DynGlobalApi,
985 auth: ApiAuth,
986 name: String,
987 settings: ConfigGenSettings,
988 amount: Amount,
989 dir: PathBuf,
990 }
991
992 impl TestConfigApi {
993 fn new(port: u16, name_suffix: u16, data_dir: &Path) -> TestConfigApi {
996 let db = MemDatabase::new().into_database();
997
998 let name = format!("peer{name_suffix}");
999 let api_bind = format!("127.0.0.1:{port}").parse().expect("parses");
1000 let api_url: SafeUrl = format!("ws://127.0.0.1:{port}").parse().expect("parses");
1001 let p2p_bind = format!("127.0.0.1:{}", port + 1).parse().expect("parses");
1002 let p2p_url = format!("fedimint://127.0.0.1:{}", port + 1)
1003 .parse()
1004 .expect("parses");
1005 let module_inits = ServerModuleInitRegistry::from_iter([DummyInit.into()]);
1006 let mut modules = ServerModuleConfigGenParamsRegistry::default();
1007 modules.attach_config_gen_params_by_id(0, DummyInit::kind(), DummyGenParams::default());
1008
1009 let default_params = ConfigGenParamsRequest {
1010 meta: BTreeMap::new(),
1011 modules,
1012 };
1013 let settings = ConfigGenSettings {
1014 download_token_limit: None,
1015 p2p_bind,
1016 api_bind,
1017 p2p_url,
1018 api_url: api_url.clone(),
1019 default_params,
1020 max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
1021 registry: ServerModuleInitRegistry::from(vec![DynServerModuleInit::from(
1022 DummyInit,
1023 )]),
1024 };
1025
1026 let dir = data_dir.join(name_suffix.to_string());
1027 fs::create_dir_all(dir.clone()).expect("Unable to create test dir");
1028
1029 let dir_clone = dir.clone();
1030 let settings_clone = settings.clone();
1031
1032 spawn("fedimint server", async move {
1033 crate::run(
1034 dir_clone,
1035 ApiSecrets::none(),
1036 settings_clone,
1037 db,
1038 "dummyversionhash".to_owned(),
1039 &module_inits,
1040 TaskGroup::new(),
1041 )
1042 .await
1043 .expect("Failed to run fedimint server");
1044 });
1045
1046 let auth = ApiAuth(format!("password-{port}"));
1048 let client = DynGlobalApi::from_pre_peer_id_admin_endpoint(api_url, &None);
1049
1050 TestConfigApi {
1051 client,
1052 auth,
1053 name,
1054 settings,
1055 amount: Amount::from_sats(u64::from(port)),
1056 dir,
1057 }
1058 }
1059
1060 async fn set_connections(&self, leader: &Option<SafeUrl>) -> FederationResult<()> {
1062 self.client
1063 .set_config_gen_connections(
1064 ConfigGenConnectionsRequest {
1065 our_name: self.name.clone(),
1066 leader_api_url: leader.clone(),
1067 },
1068 self.auth.clone(),
1069 )
1070 .await
1071 }
1072
1073 async fn status(&self) -> StatusResponse {
1075 loop {
1076 match self.client.status().await {
1077 Ok(status) => return status,
1078 Err(_) => sleep(Duration::from_millis(1000)).await,
1079 }
1080 info!(
1081 target: fedimint_logging::LOG_TEST,
1082 "Test retrying server status"
1083 );
1084 }
1085 }
1086
1087 async fn wait_status_preconfig(&self, status: ServerStatus, peers: &Vec<TestConfigApi>) {
1090 loop {
1091 let server_status = self.status().await.server;
1092 if server_status == status {
1093 for peer in peers {
1094 let peer_status = peer.status().await.server;
1095 if peer_status != server_status {
1096 info!(
1097 target: fedimint_logging::LOG_TEST,
1098 "Test retrying peer server status preconfig"
1099 );
1100 sleep(Duration::from_millis(10)).await;
1101 continue;
1102 }
1103 }
1104 break;
1105 }
1106 info!(
1107 target: fedimint_logging::LOG_TEST,
1108 "Test retrying server status preconfig"
1109 );
1110 }
1111 }
1112
1113 async fn wait_status(&self, status: ServerStatus) {
1116 loop {
1117 let response = self.client.consensus_config_gen_params().await.unwrap();
1118 let mismatched: Vec<_> = response
1119 .consensus
1120 .peers
1121 .iter()
1122 .filter(|(_, param)| param.status != Some(status.clone()))
1123 .collect();
1124 if mismatched.is_empty() {
1125 break;
1126 }
1127 info!(
1128 target: fedimint_logging::LOG_TEST,
1129 "Test retrying server status"
1130 );
1131 sleep(Duration::from_millis(10)).await;
1132 }
1133 }
1134
1135 async fn set_config_gen_params(&self) {
1137 let mut modules = ServerModuleConfigGenParamsRegistry::default();
1138 modules.attach_config_gen_params_by_id(
1139 0,
1140 DummyInit::kind(),
1141 DummyGenParams {
1142 local: DummyGenParamsLocal,
1143 consensus: DummyGenParamsConsensus {
1144 tx_fee: self.amount,
1145 },
1146 },
1147 );
1148 let request = ConfigGenParamsRequest {
1149 meta: BTreeMap::from([("\"test\"".to_string(), self.name.clone())]),
1150 modules,
1151 };
1152
1153 self.client
1154 .set_config_gen_params(request, self.auth.clone())
1155 .await
1156 .unwrap();
1157 }
1158
1159 fn read_config(&self) -> ServerConfig {
1161 let auth = fs::read_to_string(self.dir.join(PLAINTEXT_PASSWORD));
1162 read_server_config(&auth.unwrap(), &self.dir).unwrap()
1163 }
1164 }
1165
1166 #[tokio::test(flavor = "multi_thread")]
1167 async fn test_config_api() {
1168 const PEER_NUM: u16 = 4;
1169 const PORTS_PER_PEER: u16 = 2;
1170 let _ = TracingSetup::default().init();
1171 let (data_dir, _maybe_tmp_dir_guard) = test_dir("test-config-api");
1172 let base_port = port_alloc(PEER_NUM * PORTS_PER_PEER).unwrap();
1173
1174 let mut followers = vec![];
1175 let mut test_config = TestConfigApi::new(base_port, 0, &data_dir);
1176
1177 for i in 1..PEER_NUM {
1178 let port = base_port + (i * PORTS_PER_PEER);
1179 let follower = TestConfigApi::new(port, i, &data_dir);
1180 followers.push(follower);
1181 }
1182
1183 test_config = validate_leader_setup(test_config).await;
1184
1185 for follower in &mut followers {
1187 assert_eq!(
1188 follower.status().await.server,
1189 ServerStatus::AwaitingPassword
1190 );
1191 follower
1192 .client
1193 .set_password(follower.auth.clone())
1194 .await
1195 .unwrap();
1196 let leader_url = Some(test_config.settings.api_url.clone());
1197 follower.set_connections(&leader_url).await.unwrap();
1198 follower.name = format!("{}_", follower.name);
1199 follower.set_connections(&leader_url).await.unwrap();
1200 follower.set_config_gen_params().await;
1201 }
1202
1203 validate_full_setup(test_config, followers).await;
1205 }
1206
1207 #[tokio::test(flavor = "multi_thread")]
1208 #[ignore] async fn test_restart_setup() {
1210 const PEER_NUM: u16 = 4;
1211 const PORTS_PER_PEER: u16 = 2;
1212 let _ = TracingSetup::default().init();
1213 let (data_dir, _maybe_tmp_dir_guard) = test_dir("test-restart-setup");
1214 let base_port = port_alloc(PEER_NUM * PORTS_PER_PEER).unwrap();
1215
1216 let mut followers = vec![];
1217 let mut test_config = TestConfigApi::new(base_port, 0, &data_dir);
1218
1219 for i in 1..PEER_NUM {
1220 let port = base_port + (i * PORTS_PER_PEER);
1221 let follower = TestConfigApi::new(port, i, &data_dir);
1222 followers.push(follower);
1223 }
1224
1225 test_config = validate_leader_setup(test_config).await;
1226
1227 for follower in &mut followers {
1229 assert_eq!(
1230 follower.status().await.server,
1231 ServerStatus::AwaitingPassword
1232 );
1233 follower
1234 .client
1235 .set_password(follower.auth.clone())
1236 .await
1237 .unwrap();
1238 let leader_url = Some(test_config.settings.api_url.clone());
1239 follower.set_connections(&leader_url).await.unwrap();
1240 follower.name = format!("{}_", follower.name);
1241 follower.set_connections(&leader_url).await.unwrap();
1242 follower.set_config_gen_params().await;
1243 }
1244 test_config
1245 .wait_status(ServerStatus::SharingConfigGenParams)
1246 .await;
1247
1248 test_config
1250 .client
1251 .restart_federation_setup(test_config.auth.clone())
1252 .await
1253 .unwrap();
1254
1255 for peer in &followers {
1258 peer.client
1259 .restart_federation_setup(peer.auth.clone())
1260 .await
1261 .ok();
1262 }
1263
1264 test_config
1266 .wait_status_preconfig(ServerStatus::SetupRestarted, &followers)
1267 .await;
1268 test_config
1269 .wait_status_preconfig(ServerStatus::AwaitingPassword, &followers)
1270 .await;
1271
1272 test_config = validate_leader_setup(test_config).await;
1273
1274 for follower in &mut followers {
1276 assert_eq!(
1277 follower.status().await.server,
1278 ServerStatus::AwaitingPassword
1279 );
1280 follower
1281 .client
1282 .set_password(follower.auth.clone())
1283 .await
1284 .unwrap();
1285 let leader_url = Some(test_config.settings.api_url.clone());
1286 follower.set_connections(&leader_url).await.unwrap();
1287 follower.set_config_gen_params().await;
1288 }
1289
1290 validate_full_setup(test_config, followers).await;
1292 }
1293
1294 async fn validate_leader_setup(mut leader: TestConfigApi) -> TestConfigApi {
1296 assert_eq!(leader.status().await.server, ServerStatus::AwaitingPassword);
1297
1298 leader
1300 .client
1301 .set_password(leader.auth.clone())
1302 .await
1303 .unwrap();
1304 assert!(leader
1305 .client
1306 .set_password(leader.auth.clone())
1307 .await
1308 .is_err());
1309
1310 leader.set_connections(&None).await.unwrap();
1312 leader.name = "leader".to_string();
1313 leader.set_connections(&None).await.unwrap();
1314
1315 let _ = leader
1317 .client
1318 .get_default_config_gen_params(leader.auth.clone())
1319 .await
1320 .unwrap();
1321 leader.set_config_gen_params().await;
1322
1323 leader
1324 }
1325
1326 async fn validate_full_setup(leader: TestConfigApi, mut followers: Vec<TestConfigApi>) {
1328 let peers = leader.client.get_config_gen_peers().await.unwrap();
1330 let names: Vec<_> = peers.into_iter().map(|peer| peer.name).sorted().collect();
1331 assert_eq!(names, vec!["leader", "peer1_", "peer2_", "peer3_"]);
1332
1333 leader
1334 .wait_status(ServerStatus::SharingConfigGenParams)
1335 .await;
1336
1337 let mut configs = vec![];
1339 for peer in &followers {
1340 configs.push(peer.client.consensus_config_gen_params().await.unwrap());
1341 }
1342 let mut consensus: Vec<_> = configs.iter().map(|p| p.consensus.clone()).collect();
1344 consensus.dedup();
1345 assert_eq!(consensus.len(), 1);
1346 let ids: BTreeSet<_> = configs.iter().map(|p| p.our_current_id).collect();
1348 assert_eq!(ids.len(), followers.len());
1349
1350 let leader_amount = leader.amount;
1352 let leader_name = leader.name.clone();
1353 followers.push(leader);
1354 let all_peers = Arc::new(followers);
1355 let (results, ()) = tokio::join!(
1356 join_all(
1357 all_peers
1358 .iter()
1359 .map(|peer| peer.client.run_dkg(peer.auth.clone()))
1360 ),
1361 all_peers[0].wait_status(ServerStatus::VerifyingConfigs)
1362 );
1363 for result in results {
1364 result.expect("DKG failed");
1365 }
1366
1367 let mut hashes = HashSet::new();
1369 for peer in all_peers.iter() {
1370 peer.wait_status(ServerStatus::VerifyingConfigs).await;
1371 hashes.insert(
1372 peer.client
1373 .get_verify_config_hash(peer.auth.clone())
1374 .await
1375 .unwrap(),
1376 );
1377 }
1378 assert_eq!(hashes.len(), 1);
1379
1380 for peer in all_peers.iter() {
1382 peer.client.verified_configs(peer.auth.clone()).await.ok();
1383 }
1384
1385 for peer in all_peers.iter() {
1387 peer.client.start_consensus(peer.auth.clone()).await.ok();
1388 }
1389
1390 sleep(Duration::from_secs(5)).await;
1391
1392 for peer in all_peers.iter() {
1393 assert_eq!(peer.status().await.server, ServerStatus::ConsensusRunning);
1394
1395 let cfg = peer.read_config(); let dummy: DummyConfig = cfg.get_module_config_typed(0).unwrap();
1398 assert_eq!(dummy.consensus.tx_fee, leader_amount);
1399 assert_eq!(cfg.consensus.meta["\"test\""], leader_name);
1400 }
1401 }
1402}