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
239 if let QueryStep::Success(response) = strategy.process_error(peer, &e) {
244 return Ok(response);
245 }
246
247 peer_errors.insert(peer, e);
248 }
249 }
250
251 if peer_errors.len() == peer_error_threshold {
252 return Err(FederationError::peer_errors(
253 method.clone(),
254 params.params.clone(),
255 peer_errors,
256 ));
257 }
258 }
259 }
260
261 #[instrument(target = LOG_CLIENT_NET_API, level = "debug", skip(self, strategy))]
262 async fn request_with_strategy_retry<PR: DeserializeOwned + MaybeSend, FR: Debug>(
263 &self,
264 mut strategy: impl QueryStrategy<PR, FR> + MaybeSend,
265 method: String,
266 params: ApiRequestErased,
267 ) -> FR {
268 #[cfg(not(target_family = "wasm"))]
272 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _> + Send>>>::new();
273 #[cfg(target_family = "wasm")]
274 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _>>>>::new();
275
276 for peer in self.all_peers() {
277 futures.push(Box::pin({
278 let method = &method;
279 let params = ¶ms;
280 async move {
281 let response = util::retry(
282 format!("api-request-{method}-{peer}"),
283 api_networking_backoff(),
284 || async {
285 self.request_single_peer(method.clone(), params.clone(), *peer)
286 .await
287 .inspect_err(|e| {
288 e.report_if_unusual(*peer, "QueryWithStrategyRetry");
289 })
290 .map_err(|e| anyhow!(e.to_string()))
291 },
292 )
293 .await
294 .expect("Number of retries has no limit");
295
296 (*peer, response)
297 }
298 }));
299 }
300
301 loop {
302 let (peer, response) = match futures.next().await {
303 Some(t) => t,
304 None => pending().await,
305 };
306
307 match strategy.process(peer, response) {
308 QueryStep::Retry(peers) => {
309 for peer in peers {
310 futures.push(Box::pin({
311 let method = &method;
312 let params = ¶ms;
313 async move {
314 let response = util::retry(
315 format!("api-request-{method}-{peer}"),
316 api_networking_backoff(),
317 || async {
318 self.request_single_peer(
319 method.clone(),
320 params.clone(),
321 peer,
322 )
323 .await
324 .inspect_err(|err| {
325 if err.is_unusual() {
326 debug!(target: LOG_CLIENT_NET_API, err = %err.fmt_compact(), "Unusual peer error");
327 }
328 })
329 .map_err(|e| anyhow!(e.to_string()))
330 },
331 )
332 .await
333 .expect("Number of retries has no limit");
334
335 (peer, response)
336 }
337 }));
338 }
339 }
340 QueryStep::Success(response) => return response,
341 QueryStep::Failure(e) => {
342 warn!(target: LOG_CLIENT_NET_API, "Query strategy returned non-retryable failure for peer {peer}: {e}");
343 }
344 QueryStep::Continue => {}
345 }
346 }
347 }
348
349 async fn request_current_consensus<Ret>(
350 &self,
351 method: String,
352 params: ApiRequestErased,
353 ) -> FederationResult<Ret>
354 where
355 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
356 {
357 self.request_with_strategy(
358 ThresholdConsensus::new(self.all_peers().to_num_peers()),
359 method,
360 params,
361 )
362 .await
363 }
364
365 async fn request_current_consensus_retry<Ret>(
366 &self,
367 method: String,
368 params: ApiRequestErased,
369 ) -> Ret
370 where
371 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
372 {
373 self.request_with_strategy_retry(
374 ThresholdConsensus::new(self.all_peers().to_num_peers()),
375 method,
376 params,
377 )
378 .await
379 }
380
381 async fn request_admin<Ret>(
382 &self,
383 method: &str,
384 params: ApiRequestErased,
385 auth: ApiAuth,
386 ) -> FederationResult<Ret>
387 where
388 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
389 {
390 let Some(self_peer_id) = self.self_peer() else {
391 return Err(FederationError::general(
392 method,
393 params,
394 anyhow::format_err!("Admin peer_id not set"),
395 ));
396 };
397
398 self.request_single_peer_federation(method.into(), params.with_auth(auth), self_peer_id)
399 .await
400 }
401
402 async fn request_admin_no_auth<Ret>(
403 &self,
404 method: &str,
405 params: ApiRequestErased,
406 ) -> FederationResult<Ret>
407 where
408 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
409 {
410 let Some(self_peer_id) = self.self_peer() else {
411 return Err(FederationError::general(
412 method,
413 params,
414 anyhow::format_err!("Admin peer_id not set"),
415 ));
416 };
417
418 self.request_single_peer_federation(method.into(), params, self_peer_id)
419 .await
420 }
421}
422
423#[apply(async_trait_maybe_send!)]
424impl<T: ?Sized> FederationApiExt for T where T: IRawFederationApi {}
425
426pub trait IModuleFederationApi: IRawFederationApi {}
428
429dyn_newtype_define! {
430 #[derive(Clone)]
431 pub DynModuleApi(Arc<IModuleFederationApi>)
432}
433
434dyn_newtype_define! {
435 #[derive(Clone)]
436 pub DynGlobalApi(Arc<IGlobalFederationApi>)
437}
438
439impl AsRef<dyn IGlobalFederationApi + 'static> for DynGlobalApi {
440 fn as_ref(&self) -> &(dyn IGlobalFederationApi + 'static) {
441 self.inner.as_ref()
442 }
443}
444
445impl DynGlobalApi {
446 pub fn new(
447 connectors: ConnectorRegistry,
448 peers: BTreeMap<PeerId, SafeUrl>,
449 api_secret: Option<&str>,
450 ) -> anyhow::Result<Self> {
451 Ok(GlobalFederationApiWithCache::new(FederationApi::new(
452 connectors, peers, None, api_secret,
453 ))
454 .into())
455 }
456 pub fn new_admin(
457 connectors: ConnectorRegistry,
458 peer: PeerId,
459 url: SafeUrl,
460 api_secret: Option<&str>,
461 ) -> anyhow::Result<DynGlobalApi> {
462 Ok(GlobalFederationApiWithCache::new(FederationApi::new(
463 connectors,
464 [(peer, url)].into(),
465 Some(peer),
466 api_secret,
467 ))
468 .into())
469 }
470
471 pub fn new_admin_setup(connectors: ConnectorRegistry, url: SafeUrl) -> anyhow::Result<Self> {
472 Self::new_admin(
475 connectors,
476 PeerId::from(1024),
477 url,
478 None,
480 )
481 }
482}
483
484#[apply(async_trait_maybe_send!)]
486pub trait IGlobalFederationApi: IRawFederationApi {
487 async fn submit_transaction(
488 &self,
489 tx: Transaction,
490 ) -> SerdeModuleEncoding<TransactionSubmissionOutcome>;
491
492 async fn await_block(
493 &self,
494 block_index: u64,
495 decoders: &ModuleDecoderRegistry,
496 ) -> anyhow::Result<SessionOutcome>;
497
498 async fn get_session_status(
499 &self,
500 block_index: u64,
501 decoders: &ModuleDecoderRegistry,
502 core_api_version: ApiVersion,
503 broadcast_public_keys: Option<&BTreeMap<PeerId, secp256k1::PublicKey>>,
504 ) -> anyhow::Result<SessionStatus>;
505
506 async fn session_count(&self) -> FederationResult<u64>;
507
508 async fn await_transaction(&self, txid: TransactionId) -> TransactionId;
509
510 async fn upload_backup(&self, request: &SignedBackupRequest) -> FederationResult<()>;
511
512 async fn download_backup(
513 &self,
514 id: &secp256k1::PublicKey,
515 ) -> FederationResult<BTreeMap<PeerId, Option<ClientBackupSnapshot>>>;
516
517 async fn setup_status(&self, auth: ApiAuth) -> FederationResult<SetupStatus>;
518
519 async fn set_local_params(
520 &self,
521 name: String,
522 federation_name: Option<String>,
523 disable_base_fees: Option<bool>,
524 enabled_modules: Option<BTreeSet<ModuleKind>>,
525 federation_size: Option<u32>,
526 auth: ApiAuth,
527 ) -> FederationResult<String>;
528
529 async fn add_peer_connection_info(
530 &self,
531 info: String,
532 auth: ApiAuth,
533 ) -> FederationResult<String>;
534
535 async fn reset_peer_setup_codes(&self, auth: ApiAuth) -> FederationResult<()>;
537
538 async fn get_setup_code(&self, auth: ApiAuth) -> FederationResult<Option<String>>;
540
541 async fn start_dkg(&self, auth: ApiAuth) -> FederationResult<()>;
545
546 async fn status(&self) -> FederationResult<StatusResponse>;
548
549 async fn audit(&self, auth: ApiAuth) -> FederationResult<AuditSummary>;
551
552 async fn guardian_config_backup(&self, auth: ApiAuth)
554 -> FederationResult<GuardianConfigBackup>;
555
556 async fn auth(&self, auth: ApiAuth) -> FederationResult<()>;
558
559 async fn restart_federation_setup(&self, auth: ApiAuth) -> FederationResult<()>;
560
561 async fn submit_api_announcement(
563 &self,
564 peer_id: PeerId,
565 announcement: SignedApiAnnouncement,
566 ) -> FederationResult<()>;
567
568 async fn api_announcements(
569 &self,
570 guardian: PeerId,
571 ) -> ServerResult<BTreeMap<PeerId, SignedApiAnnouncement>>;
572
573 async fn sign_api_announcement(
574 &self,
575 api_url: SafeUrl,
576 auth: ApiAuth,
577 ) -> FederationResult<SignedApiAnnouncement>;
578
579 async fn submit_guardian_metadata(
581 &self,
582 peer_id: PeerId,
583 metadata: SignedGuardianMetadata,
584 ) -> FederationResult<()>;
585
586 async fn guardian_metadata(
587 &self,
588 guardian: PeerId,
589 ) -> ServerResult<BTreeMap<PeerId, SignedGuardianMetadata>>;
590
591 async fn sign_guardian_metadata(
592 &self,
593 metadata: fedimint_core::net::guardian_metadata::GuardianMetadata,
594 auth: ApiAuth,
595 ) -> FederationResult<SignedGuardianMetadata>;
596
597 async fn shutdown(&self, session: Option<u64>, auth: ApiAuth) -> FederationResult<()>;
598
599 async fn fedimintd_version(&self, peer_id: PeerId) -> ServerResult<String>;
601
602 async fn backup_statistics(&self, auth: ApiAuth) -> FederationResult<BackupStatistics>;
604
605 async fn get_invite_code(&self, guardian: PeerId) -> ServerResult<InviteCode>;
608
609 async fn chain_id(&self) -> FederationResult<ChainId>;
612}
613
614pub fn deserialize_outcome<R>(
615 outcome: &SerdeOutputOutcome,
616 module_decoder: &Decoder,
617) -> OutputOutcomeResult<R>
618where
619 R: OutputOutcome + MaybeSend,
620{
621 let dyn_outcome = outcome
622 .try_into_inner_known_module_kind(module_decoder)
623 .map_err(|e| OutputOutcomeError::ResponseDeserialization(e.into()))?;
624
625 let source_instance = dyn_outcome.module_instance_id();
626
627 dyn_outcome.as_any().downcast_ref().cloned().ok_or_else(|| {
628 let target_type = std::any::type_name::<R>();
629 OutputOutcomeError::ResponseDeserialization(anyhow!(
630 "Could not downcast output outcome with instance id {source_instance} to {target_type}"
631 ))
632 })
633}
634
635#[derive(Clone, Debug)]
646pub struct FederationApi {
647 peers: BTreeMap<PeerId, SafeUrl>,
649 peers_keys: BTreeSet<PeerId>,
651 admin_id: Option<PeerId>,
653 module_id: Option<ModuleInstanceId>,
655 api_secret: Option<String>,
657 connection_pool: ConnectionPool<dyn IGuardianConnection>,
659}
660
661impl FederationApi {
662 pub fn new(
663 connectors: ConnectorRegistry,
664 peers: BTreeMap<PeerId, SafeUrl>,
665 admin_peer_id: Option<PeerId>,
666 api_secret: Option<&str>,
667 ) -> Self {
668 Self {
669 peers_keys: peers.keys().copied().collect(),
670 peers,
671 admin_id: admin_peer_id,
672 module_id: None,
673 api_secret: api_secret.map(ToOwned::to_owned),
674 connection_pool: ConnectionPool::new(connectors),
675 }
676 }
677
678 async fn get_or_create_connection(
679 &self,
680 url: &SafeUrl,
681 api_secret: Option<&str>,
682 ) -> ServerResult<DynGuaridianConnection> {
683 self.connection_pool
684 .get_or_create_connection(url, api_secret, |url, api_secret, connectors| async move {
685 let conn = connectors
686 .connect_guardian(&url, api_secret.as_deref())
687 .await?;
688 Ok(conn)
689 })
690 .await
691 }
692
693 async fn request(
694 &self,
695 peer: PeerId,
696 method: ApiMethod,
697 request: ApiRequestErased,
698 ) -> ServerResult<Value> {
699 trace!(target: LOG_CLIENT_NET_API, %peer, %method, "Api request");
700 let url = self
701 .peers
702 .get(&peer)
703 .ok_or_else(|| ServerError::InvalidPeerId { peer_id: peer })?;
704 let conn = self
705 .get_or_create_connection(url, self.api_secret.as_deref())
706 .await
707 .context("Failed to connect to peer")
708 .map_err(ServerError::Connection)?;
709
710 let method_str = method.to_string();
711 let peer_str = peer.to_string();
712 let timer = CLIENT_API_REQUEST_DURATION_SECONDS
713 .with_label_values(&[&method_str, &peer_str])
714 .start_timer_ext();
715
716 let res = conn.request(method.clone(), request).await;
717
718 timer.observe_duration();
719
720 let result_label = if res.is_ok() { "success" } else { "error" }.to_string();
721 CLIENT_API_REQUESTS_TOTAL
722 .with_label_values(&[&method_str, &peer_str, &result_label])
723 .inc();
724
725 trace!(target: LOG_CLIENT_NET_API, ?method, res_ok = res.is_ok(), "Api response");
726
727 res
728 }
729
730 pub fn get_active_connection_receiver(&self) -> watch::Receiver<BTreeSet<SafeUrl>> {
734 self.connection_pool.get_active_connection_receiver()
735 }
736}
737
738impl IModuleFederationApi for FederationApi {}
739
740#[apply(async_trait_maybe_send!)]
741impl IRawFederationApi for FederationApi {
742 fn all_peers(&self) -> &BTreeSet<PeerId> {
743 &self.peers_keys
744 }
745
746 fn self_peer(&self) -> Option<PeerId> {
747 self.admin_id
748 }
749
750 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
751 FederationApi {
752 api_secret: self.api_secret.clone(),
753 peers: self.peers.clone(),
754 peers_keys: self.peers_keys.clone(),
755 admin_id: self.admin_id,
756 module_id: Some(id),
757 connection_pool: self.connection_pool.clone(),
758 }
759 .into()
760 }
761
762 #[instrument(
763 target = LOG_CLIENT_NET_API,
764 skip_all,
765 fields(
766 peer_id = %peer_id,
767 method = %method,
768 params = %params.params,
769 )
770 )]
771 async fn request_raw(
772 &self,
773 peer_id: PeerId,
774 method: &str,
775 params: &ApiRequestErased,
776 ) -> ServerResult<Value> {
777 let method = match self.module_id {
778 Some(module_id) => ApiMethod::Module(module_id, method.to_string()),
779 None => ApiMethod::Core(method.to_string()),
780 };
781
782 self.request(peer_id, method, params.clone()).await
783 }
784
785 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>> {
786 let peers = self.peers.clone();
787 let pool = self.connection_pool.clone();
788 let active_rx = self.connection_pool.get_active_connection_receiver();
789
790 let membership_ticks = WatchStream::new(active_rx.clone()).map(|_| ());
796 let path_change_ticks =
797 WatchStream::new(self.connection_pool.connectivity_change_notifier()).map(|_| ());
798 let ticks = futures::stream::select(membership_ticks, path_change_ticks);
799
800 ticks
801 .map(move |()| {
802 let active_urls = active_rx.borrow().clone();
803 peers
804 .iter()
805 .map(|(peer_id, url)| {
806 let status = if active_urls.contains(url) {
807 match pool.connectivity(url) {
816 Connectivity::Unknown => PeerStatus::Disconnected,
817 connectivity => PeerStatus::Connected(connectivity),
818 }
819 } else {
820 PeerStatus::Disconnected
821 };
822 (*peer_id, status)
823 })
824 .collect()
825 })
826 .boxed()
827 }
828 async fn wait_for_initialized_connections(&self) {
829 self.connection_pool
830 .wait_for_initialized_connections()
831 .await;
832 }
833
834 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection> {
835 let url = self
836 .peers
837 .get(&peer_id)
838 .ok_or_else(|| ServerError::InvalidPeerId { peer_id })?;
839 self.get_or_create_connection(url, self.api_secret.as_deref())
840 .await
841 }
842}
843
844#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
846pub struct LegacyFederationStatus {
847 pub session_count: u64,
848 pub status_by_peer: BTreeMap<PeerId, LegacyPeerStatus>,
849 pub peers_online: u64,
850 pub peers_offline: u64,
851 pub peers_flagged: u64,
854 pub scheduled_shutdown: Option<u64>,
855}
856
857#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
858pub struct LegacyPeerStatus {
859 pub last_contribution: Option<u64>,
860 pub connection_status: LegacyP2PConnectionStatus,
861 pub flagged: bool,
864}
865
866#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
867#[serde(rename_all = "snake_case")]
868pub enum LegacyP2PConnectionStatus {
869 #[default]
870 Disconnected,
871 Connected,
872}
873
874#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
875pub struct StatusResponse {
876 pub server: ServerStatusLegacy,
877 pub federation: Option<LegacyFederationStatus>,
878}
879
880#[cfg(test)]
881mod tests;