1mod error;
2pub mod global_api;
3
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt::Debug;
6use std::future::pending;
7use std::pin::Pin;
8use std::result;
9use std::sync::Arc;
10
11use anyhow::{Context, anyhow};
12use bitcoin::secp256k1;
13pub use error::{FederationError, OutputOutcomeError};
14pub use fedimint_connectors::ServerResult;
15pub use fedimint_connectors::error::ServerError;
16use fedimint_connectors::{
17 ConnectionPool, Connectivity, ConnectorRegistry, DynGuaridianConnection, IGuardianConnection,
18 PeerStatus,
19};
20use fedimint_core::admin_client::{GuardianConfigBackup, ServerStatusLegacy, SetupStatus};
21use fedimint_core::backup::{BackupStatistics, ClientBackupSnapshot};
22use fedimint_core::core::backup::SignedBackupRequest;
23use fedimint_core::core::{Decoder, DynOutputOutcome, ModuleInstanceId, ModuleKind, OutputOutcome};
24use fedimint_core::encoding::{Decodable, Encodable};
25use fedimint_core::invite_code::InviteCode;
26use fedimint_core::module::audit::AuditSummary;
27use fedimint_core::module::registry::ModuleDecoderRegistry;
28use fedimint_core::module::{
29 ApiAuth, ApiMethod, ApiRequestErased, ApiVersion, SerdeModuleEncoding,
30};
31use fedimint_core::net::api_announcement::SignedApiAnnouncement;
32use fedimint_core::net::guardian_metadata::SignedGuardianMetadata;
33use fedimint_core::session_outcome::{SessionOutcome, SessionStatus};
34use fedimint_core::task::{MaybeSend, MaybeSync};
35use fedimint_core::transaction::{Transaction, TransactionSubmissionOutcome};
36use fedimint_core::util::backoff_util::api_networking_backoff;
37use fedimint_core::util::{FmtCompact as _, SafeUrl};
38use fedimint_core::{
39 ChainId, NumPeersExt, PeerId, TransactionId, apply, async_trait_maybe_send, dyn_newtype_define,
40 util,
41};
42use fedimint_logging::LOG_CLIENT_NET_API;
43use fedimint_metrics::HistogramExt as _;
44use futures::stream::{BoxStream, FuturesUnordered};
45use futures::{Future, StreamExt};
46use global_api::with_cache::GlobalFederationApiWithCache;
47use jsonrpsee_core::DeserializeOwned;
48use serde::{Deserialize, Serialize};
49use serde_json::Value;
50use tokio::sync::watch;
51use tokio_stream::wrappers::WatchStream;
52use tracing::{debug, instrument, trace, warn};
53
54use crate::metrics::{CLIENT_API_REQUEST_DURATION_SECONDS, CLIENT_API_REQUESTS_TOTAL};
55use crate::query::{QueryStep, QueryStrategy, ThresholdConsensus};
56
57pub const VERSION_THAT_INTRODUCED_GET_SESSION_STATUS_V2: ApiVersion = ApiVersion::new(0, 5);
58
59pub const VERSION_THAT_INTRODUCED_GET_SESSION_STATUS: ApiVersion =
60 ApiVersion { major: 0, minor: 1 };
61
62pub const VERSION_THAT_INTRODUCED_AWAIT_OUTPUTS_OUTCOMES: ApiVersion = ApiVersion::new(0, 8);
63pub type FederationResult<T> = Result<T, FederationError>;
64pub type SerdeOutputOutcome = SerdeModuleEncoding<DynOutputOutcome>;
65
66pub type OutputOutcomeResult<O> = result::Result<O, OutputOutcomeError>;
67
68#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable)]
72pub struct ApiVersionSet {
73 pub core: ApiVersion,
74 pub modules: BTreeMap<ModuleInstanceId, ApiVersion>,
75}
76
77#[apply(async_trait_maybe_send!)]
79pub trait IRawFederationApi: Debug + MaybeSend + MaybeSync {
80 fn all_peers(&self) -> &BTreeSet<PeerId>;
88
89 fn self_peer(&self) -> Option<PeerId>;
94
95 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi;
96
97 async fn request_raw(
99 &self,
100 peer_id: PeerId,
101 method: &str,
102 params: &ApiRequestErased,
103 ) -> ServerResult<Value>;
104
105 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>>;
120 async fn wait_for_initialized_connections(&self);
125
126 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection>;
132}
133
134#[apply(async_trait_maybe_send!)]
137pub trait FederationApiExt: IRawFederationApi {
138 async fn request_single_peer<Ret>(
139 &self,
140 method: String,
141 params: ApiRequestErased,
142 peer: PeerId,
143 ) -> ServerResult<Ret>
144 where
145 Ret: DeserializeOwned,
146 {
147 self.request_raw(peer, &method, ¶ms)
148 .await
149 .and_then(|v| {
150 serde_json::from_value(v)
151 .map_err(|e| ServerError::ResponseDeserialization(e.into()))
152 })
153 }
154
155 async fn request_single_peer_federation<FedRet>(
156 &self,
157 method: String,
158 params: ApiRequestErased,
159 peer_id: PeerId,
160 ) -> FederationResult<FedRet>
161 where
162 FedRet: serde::de::DeserializeOwned + Eq + Debug + Clone + MaybeSend,
163 {
164 self.request_raw(peer_id, &method, ¶ms)
165 .await
166 .and_then(|v| {
167 serde_json::from_value(v)
168 .map_err(|e| ServerError::ResponseDeserialization(e.into()))
169 })
170 .map_err(|e| error::FederationError::new_one_peer(peer_id, method, params, e))
171 }
172
173 #[instrument(target = LOG_CLIENT_NET_API, skip_all, fields(method=method))]
176 async fn request_with_strategy<PR: DeserializeOwned, FR: Debug>(
177 &self,
178 mut strategy: impl QueryStrategy<PR, FR> + MaybeSend,
179 method: String,
180 params: ApiRequestErased,
181 ) -> FederationResult<FR> {
182 #[cfg(not(target_family = "wasm"))]
186 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _> + Send>>>::new();
187 #[cfg(target_family = "wasm")]
188 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _>>>>::new();
189
190 for peer in self.all_peers() {
191 futures.push(Box::pin({
192 let method = &method;
193 let params = ¶ms;
194 async move {
195 let result = self
196 .request_single_peer(method.clone(), params.clone(), *peer)
197 .await;
198
199 (*peer, result)
200 }
201 }));
202 }
203
204 let mut peer_errors = BTreeMap::new();
205 let peer_error_threshold = self.all_peers().to_num_peers().one_honest();
206
207 loop {
208 let (peer, result) = futures
209 .next()
210 .await
211 .expect("Query strategy ran out of peers to query without returning a result");
212
213 match result {
214 Ok(response) => match strategy.process(peer, response) {
215 QueryStep::Retry(peers) => {
216 for peer in peers {
217 futures.push(Box::pin({
218 let method = &method;
219 let params = ¶ms;
220 async move {
221 let result = self
222 .request_single_peer(method.clone(), params.clone(), peer)
223 .await;
224
225 (peer, result)
226 }
227 }));
228 }
229 }
230 QueryStep::Success(response) => return Ok(response),
231 QueryStep::Failure(e) => {
232 peer_errors.insert(peer, e);
233 }
234 QueryStep::Continue => {}
235 },
236 Err(e) => {
237 e.report_if_unusual(peer, "RequestWithStrategy");
238 peer_errors.insert(peer, e);
239 }
240 }
241
242 if peer_errors.len() == peer_error_threshold {
243 return Err(FederationError::peer_errors(
244 method.clone(),
245 params.params.clone(),
246 peer_errors,
247 ));
248 }
249 }
250 }
251
252 #[instrument(target = LOG_CLIENT_NET_API, level = "debug", skip(self, strategy))]
253 async fn request_with_strategy_retry<PR: DeserializeOwned + MaybeSend, FR: Debug>(
254 &self,
255 mut strategy: impl QueryStrategy<PR, FR> + MaybeSend,
256 method: String,
257 params: ApiRequestErased,
258 ) -> FR {
259 #[cfg(not(target_family = "wasm"))]
263 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _> + Send>>>::new();
264 #[cfg(target_family = "wasm")]
265 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _>>>>::new();
266
267 for peer in self.all_peers() {
268 futures.push(Box::pin({
269 let method = &method;
270 let params = ¶ms;
271 async move {
272 let response = util::retry(
273 format!("api-request-{method}-{peer}"),
274 api_networking_backoff(),
275 || async {
276 self.request_single_peer(method.clone(), params.clone(), *peer)
277 .await
278 .inspect_err(|e| {
279 e.report_if_unusual(*peer, "QueryWithStrategyRetry");
280 })
281 .map_err(|e| anyhow!(e.to_string()))
282 },
283 )
284 .await
285 .expect("Number of retries has no limit");
286
287 (*peer, response)
288 }
289 }));
290 }
291
292 loop {
293 let (peer, response) = match futures.next().await {
294 Some(t) => t,
295 None => pending().await,
296 };
297
298 match strategy.process(peer, response) {
299 QueryStep::Retry(peers) => {
300 for peer in peers {
301 futures.push(Box::pin({
302 let method = &method;
303 let params = ¶ms;
304 async move {
305 let response = util::retry(
306 format!("api-request-{method}-{peer}"),
307 api_networking_backoff(),
308 || async {
309 self.request_single_peer(
310 method.clone(),
311 params.clone(),
312 peer,
313 )
314 .await
315 .inspect_err(|err| {
316 if err.is_unusual() {
317 debug!(target: LOG_CLIENT_NET_API, err = %err.fmt_compact(), "Unusual peer error");
318 }
319 })
320 .map_err(|e| anyhow!(e.to_string()))
321 },
322 )
323 .await
324 .expect("Number of retries has no limit");
325
326 (peer, response)
327 }
328 }));
329 }
330 }
331 QueryStep::Success(response) => return response,
332 QueryStep::Failure(e) => {
333 warn!(target: LOG_CLIENT_NET_API, "Query strategy returned non-retryable failure for peer {peer}: {e}");
334 }
335 QueryStep::Continue => {}
336 }
337 }
338 }
339
340 async fn request_current_consensus<Ret>(
341 &self,
342 method: String,
343 params: ApiRequestErased,
344 ) -> FederationResult<Ret>
345 where
346 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
347 {
348 self.request_with_strategy(
349 ThresholdConsensus::new(self.all_peers().to_num_peers()),
350 method,
351 params,
352 )
353 .await
354 }
355
356 async fn request_current_consensus_retry<Ret>(
357 &self,
358 method: String,
359 params: ApiRequestErased,
360 ) -> Ret
361 where
362 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
363 {
364 self.request_with_strategy_retry(
365 ThresholdConsensus::new(self.all_peers().to_num_peers()),
366 method,
367 params,
368 )
369 .await
370 }
371
372 async fn request_admin<Ret>(
373 &self,
374 method: &str,
375 params: ApiRequestErased,
376 auth: ApiAuth,
377 ) -> FederationResult<Ret>
378 where
379 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
380 {
381 let Some(self_peer_id) = self.self_peer() else {
382 return Err(FederationError::general(
383 method,
384 params,
385 anyhow::format_err!("Admin peer_id not set"),
386 ));
387 };
388
389 self.request_single_peer_federation(method.into(), params.with_auth(auth), self_peer_id)
390 .await
391 }
392
393 async fn request_admin_no_auth<Ret>(
394 &self,
395 method: &str,
396 params: ApiRequestErased,
397 ) -> FederationResult<Ret>
398 where
399 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
400 {
401 let Some(self_peer_id) = self.self_peer() else {
402 return Err(FederationError::general(
403 method,
404 params,
405 anyhow::format_err!("Admin peer_id not set"),
406 ));
407 };
408
409 self.request_single_peer_federation(method.into(), params, self_peer_id)
410 .await
411 }
412}
413
414#[apply(async_trait_maybe_send!)]
415impl<T: ?Sized> FederationApiExt for T where T: IRawFederationApi {}
416
417pub trait IModuleFederationApi: IRawFederationApi {}
419
420dyn_newtype_define! {
421 #[derive(Clone)]
422 pub DynModuleApi(Arc<IModuleFederationApi>)
423}
424
425dyn_newtype_define! {
426 #[derive(Clone)]
427 pub DynGlobalApi(Arc<IGlobalFederationApi>)
428}
429
430impl AsRef<dyn IGlobalFederationApi + 'static> for DynGlobalApi {
431 fn as_ref(&self) -> &(dyn IGlobalFederationApi + 'static) {
432 self.inner.as_ref()
433 }
434}
435
436impl DynGlobalApi {
437 pub fn new(
438 connectors: ConnectorRegistry,
439 peers: BTreeMap<PeerId, SafeUrl>,
440 api_secret: Option<&str>,
441 ) -> anyhow::Result<Self> {
442 Ok(GlobalFederationApiWithCache::new(FederationApi::new(
443 connectors, peers, None, api_secret,
444 ))
445 .into())
446 }
447 pub fn new_admin(
448 connectors: ConnectorRegistry,
449 peer: PeerId,
450 url: SafeUrl,
451 api_secret: Option<&str>,
452 ) -> anyhow::Result<DynGlobalApi> {
453 Ok(GlobalFederationApiWithCache::new(FederationApi::new(
454 connectors,
455 [(peer, url)].into(),
456 Some(peer),
457 api_secret,
458 ))
459 .into())
460 }
461
462 pub fn new_admin_setup(connectors: ConnectorRegistry, url: SafeUrl) -> anyhow::Result<Self> {
463 Self::new_admin(
466 connectors,
467 PeerId::from(1024),
468 url,
469 None,
471 )
472 }
473}
474
475#[apply(async_trait_maybe_send!)]
477pub trait IGlobalFederationApi: IRawFederationApi {
478 async fn submit_transaction(
479 &self,
480 tx: Transaction,
481 ) -> SerdeModuleEncoding<TransactionSubmissionOutcome>;
482
483 async fn await_block(
484 &self,
485 block_index: u64,
486 decoders: &ModuleDecoderRegistry,
487 ) -> anyhow::Result<SessionOutcome>;
488
489 async fn get_session_status(
490 &self,
491 block_index: u64,
492 decoders: &ModuleDecoderRegistry,
493 core_api_version: ApiVersion,
494 broadcast_public_keys: Option<&BTreeMap<PeerId, secp256k1::PublicKey>>,
495 ) -> anyhow::Result<SessionStatus>;
496
497 async fn session_count(&self) -> FederationResult<u64>;
498
499 async fn await_transaction(&self, txid: TransactionId) -> TransactionId;
500
501 async fn upload_backup(&self, request: &SignedBackupRequest) -> FederationResult<()>;
502
503 async fn download_backup(
504 &self,
505 id: &secp256k1::PublicKey,
506 ) -> FederationResult<BTreeMap<PeerId, Option<ClientBackupSnapshot>>>;
507
508 async fn setup_status(&self, auth: ApiAuth) -> FederationResult<SetupStatus>;
509
510 async fn set_local_params(
511 &self,
512 name: String,
513 federation_name: Option<String>,
514 disable_base_fees: Option<bool>,
515 enabled_modules: Option<BTreeSet<ModuleKind>>,
516 federation_size: Option<u32>,
517 auth: ApiAuth,
518 ) -> FederationResult<String>;
519
520 async fn add_peer_connection_info(
521 &self,
522 info: String,
523 auth: ApiAuth,
524 ) -> FederationResult<String>;
525
526 async fn reset_peer_setup_codes(&self, auth: ApiAuth) -> FederationResult<()>;
528
529 async fn get_setup_code(&self, auth: ApiAuth) -> FederationResult<Option<String>>;
531
532 async fn start_dkg(&self, auth: ApiAuth) -> FederationResult<()>;
536
537 async fn status(&self) -> FederationResult<StatusResponse>;
539
540 async fn audit(&self, auth: ApiAuth) -> FederationResult<AuditSummary>;
542
543 async fn guardian_config_backup(&self, auth: ApiAuth)
545 -> FederationResult<GuardianConfigBackup>;
546
547 async fn auth(&self, auth: ApiAuth) -> FederationResult<()>;
549
550 async fn restart_federation_setup(&self, auth: ApiAuth) -> FederationResult<()>;
551
552 async fn submit_api_announcement(
554 &self,
555 peer_id: PeerId,
556 announcement: SignedApiAnnouncement,
557 ) -> FederationResult<()>;
558
559 async fn api_announcements(
560 &self,
561 guardian: PeerId,
562 ) -> ServerResult<BTreeMap<PeerId, SignedApiAnnouncement>>;
563
564 async fn sign_api_announcement(
565 &self,
566 api_url: SafeUrl,
567 auth: ApiAuth,
568 ) -> FederationResult<SignedApiAnnouncement>;
569
570 async fn submit_guardian_metadata(
572 &self,
573 peer_id: PeerId,
574 metadata: SignedGuardianMetadata,
575 ) -> FederationResult<()>;
576
577 async fn guardian_metadata(
578 &self,
579 guardian: PeerId,
580 ) -> ServerResult<BTreeMap<PeerId, SignedGuardianMetadata>>;
581
582 async fn sign_guardian_metadata(
583 &self,
584 metadata: fedimint_core::net::guardian_metadata::GuardianMetadata,
585 auth: ApiAuth,
586 ) -> FederationResult<SignedGuardianMetadata>;
587
588 async fn shutdown(&self, session: Option<u64>, auth: ApiAuth) -> FederationResult<()>;
589
590 async fn fedimintd_version(&self, peer_id: PeerId) -> ServerResult<String>;
592
593 async fn backup_statistics(&self, auth: ApiAuth) -> FederationResult<BackupStatistics>;
595
596 async fn get_invite_code(&self, guardian: PeerId) -> ServerResult<InviteCode>;
599
600 async fn chain_id(&self) -> FederationResult<ChainId>;
603}
604
605pub fn deserialize_outcome<R>(
606 outcome: &SerdeOutputOutcome,
607 module_decoder: &Decoder,
608) -> OutputOutcomeResult<R>
609where
610 R: OutputOutcome + MaybeSend,
611{
612 let dyn_outcome = outcome
613 .try_into_inner_known_module_kind(module_decoder)
614 .map_err(|e| OutputOutcomeError::ResponseDeserialization(e.into()))?;
615
616 let source_instance = dyn_outcome.module_instance_id();
617
618 dyn_outcome.as_any().downcast_ref().cloned().ok_or_else(|| {
619 let target_type = std::any::type_name::<R>();
620 OutputOutcomeError::ResponseDeserialization(anyhow!(
621 "Could not downcast output outcome with instance id {source_instance} to {target_type}"
622 ))
623 })
624}
625
626#[derive(Clone, Debug)]
637pub struct FederationApi {
638 peers: BTreeMap<PeerId, SafeUrl>,
640 peers_keys: BTreeSet<PeerId>,
642 admin_id: Option<PeerId>,
644 module_id: Option<ModuleInstanceId>,
646 api_secret: Option<String>,
648 connection_pool: ConnectionPool<dyn IGuardianConnection>,
650}
651
652impl FederationApi {
653 pub fn new(
654 connectors: ConnectorRegistry,
655 peers: BTreeMap<PeerId, SafeUrl>,
656 admin_peer_id: Option<PeerId>,
657 api_secret: Option<&str>,
658 ) -> Self {
659 Self {
660 peers_keys: peers.keys().copied().collect(),
661 peers,
662 admin_id: admin_peer_id,
663 module_id: None,
664 api_secret: api_secret.map(ToOwned::to_owned),
665 connection_pool: ConnectionPool::new(connectors),
666 }
667 }
668
669 async fn get_or_create_connection(
670 &self,
671 url: &SafeUrl,
672 api_secret: Option<&str>,
673 ) -> ServerResult<DynGuaridianConnection> {
674 self.connection_pool
675 .get_or_create_connection(url, api_secret, |url, api_secret, connectors| async move {
676 let conn = connectors
677 .connect_guardian(&url, api_secret.as_deref())
678 .await?;
679 Ok(conn)
680 })
681 .await
682 }
683
684 async fn request(
685 &self,
686 peer: PeerId,
687 method: ApiMethod,
688 request: ApiRequestErased,
689 ) -> ServerResult<Value> {
690 trace!(target: LOG_CLIENT_NET_API, %peer, %method, "Api request");
691 let url = self
692 .peers
693 .get(&peer)
694 .ok_or_else(|| ServerError::InvalidPeerId { peer_id: peer })?;
695 let conn = self
696 .get_or_create_connection(url, self.api_secret.as_deref())
697 .await
698 .context("Failed to connect to peer")
699 .map_err(ServerError::Connection)?;
700
701 let method_str = method.to_string();
702 let peer_str = peer.to_string();
703 let timer = CLIENT_API_REQUEST_DURATION_SECONDS
704 .with_label_values(&[&method_str, &peer_str])
705 .start_timer_ext();
706
707 let res = conn.request(method.clone(), request).await;
708
709 timer.observe_duration();
710
711 let result_label = if res.is_ok() { "success" } else { "error" }.to_string();
712 CLIENT_API_REQUESTS_TOTAL
713 .with_label_values(&[&method_str, &peer_str, &result_label])
714 .inc();
715
716 trace!(target: LOG_CLIENT_NET_API, ?method, res_ok = res.is_ok(), "Api response");
717
718 res
719 }
720
721 pub fn get_active_connection_receiver(&self) -> watch::Receiver<BTreeSet<SafeUrl>> {
725 self.connection_pool.get_active_connection_receiver()
726 }
727}
728
729impl IModuleFederationApi for FederationApi {}
730
731#[apply(async_trait_maybe_send!)]
732impl IRawFederationApi for FederationApi {
733 fn all_peers(&self) -> &BTreeSet<PeerId> {
734 &self.peers_keys
735 }
736
737 fn self_peer(&self) -> Option<PeerId> {
738 self.admin_id
739 }
740
741 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
742 FederationApi {
743 api_secret: self.api_secret.clone(),
744 peers: self.peers.clone(),
745 peers_keys: self.peers_keys.clone(),
746 admin_id: self.admin_id,
747 module_id: Some(id),
748 connection_pool: self.connection_pool.clone(),
749 }
750 .into()
751 }
752
753 #[instrument(
754 target = LOG_CLIENT_NET_API,
755 skip_all,
756 fields(
757 peer_id = %peer_id,
758 method = %method,
759 params = %params.params,
760 )
761 )]
762 async fn request_raw(
763 &self,
764 peer_id: PeerId,
765 method: &str,
766 params: &ApiRequestErased,
767 ) -> ServerResult<Value> {
768 let method = match self.module_id {
769 Some(module_id) => ApiMethod::Module(module_id, method.to_string()),
770 None => ApiMethod::Core(method.to_string()),
771 };
772
773 self.request(peer_id, method, params.clone()).await
774 }
775
776 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>> {
777 let peers = self.peers.clone();
778 let pool = self.connection_pool.clone();
779 let active_rx = self.connection_pool.get_active_connection_receiver();
780
781 let membership_ticks = WatchStream::new(active_rx.clone()).map(|_| ());
787 let path_change_ticks =
788 WatchStream::new(self.connection_pool.connectivity_change_notifier()).map(|_| ());
789 let ticks = futures::stream::select(membership_ticks, path_change_ticks);
790
791 ticks
792 .map(move |()| {
793 let active_urls = active_rx.borrow().clone();
794 peers
795 .iter()
796 .map(|(peer_id, url)| {
797 let status = if active_urls.contains(url) {
798 match pool.connectivity(url) {
807 Connectivity::Unknown => PeerStatus::Disconnected,
808 connectivity => PeerStatus::Connected(connectivity),
809 }
810 } else {
811 PeerStatus::Disconnected
812 };
813 (*peer_id, status)
814 })
815 .collect()
816 })
817 .boxed()
818 }
819 async fn wait_for_initialized_connections(&self) {
820 self.connection_pool
821 .wait_for_initialized_connections()
822 .await;
823 }
824
825 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection> {
826 let url = self
827 .peers
828 .get(&peer_id)
829 .ok_or_else(|| ServerError::InvalidPeerId { peer_id })?;
830 self.get_or_create_connection(url, self.api_secret.as_deref())
831 .await
832 }
833}
834
835#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
837pub struct LegacyFederationStatus {
838 pub session_count: u64,
839 pub status_by_peer: BTreeMap<PeerId, LegacyPeerStatus>,
840 pub peers_online: u64,
841 pub peers_offline: u64,
842 pub peers_flagged: u64,
845 pub scheduled_shutdown: Option<u64>,
846}
847
848#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
849pub struct LegacyPeerStatus {
850 pub last_contribution: Option<u64>,
851 pub connection_status: LegacyP2PConnectionStatus,
852 pub flagged: bool,
855}
856
857#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
858#[serde(rename_all = "snake_case")]
859pub enum LegacyP2PConnectionStatus {
860 #[default]
861 Disconnected,
862 Connected,
863}
864
865#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
866pub struct StatusResponse {
867 pub server: ServerStatusLegacy,
868 pub federation: Option<LegacyFederationStatus>,
869}
870
871#[cfg(test)]
872mod tests;