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 bitcoin::secp256k1;
12pub use error::{
13 ClientConfigDownloadError, FederationError, FederationGeneralError, OutputOutcomeError,
14};
15pub use fedimint_connectors::ServerResult;
16pub use fedimint_connectors::error::ServerError;
17use fedimint_connectors::{
18 ConnectionPool, Connectivity, ConnectorRegistry, DynGuaridianConnection, IGuardianConnection,
19 PeerStatus,
20};
21use fedimint_core::admin_client::{GuardianConfigBackup, ServerStatusLegacy, SetupStatus};
22use fedimint_core::backup::{BackupStatistics, ClientBackupSnapshot};
23use fedimint_core::core::backup::SignedBackupRequest;
24use fedimint_core::core::{Decoder, DynOutputOutcome, ModuleInstanceId, ModuleKind, OutputOutcome};
25use fedimint_core::encoding::{Decodable, Encodable};
26use fedimint_core::invite_code::InviteCode;
27use fedimint_core::module::audit::AuditSummary;
28use fedimint_core::module::registry::ModuleDecoderRegistry;
29use fedimint_core::module::{
30 ApiAuth, ApiMethod, ApiRequestErased, ApiVersion, SerdeModuleEncoding,
31};
32use fedimint_core::net::api_announcement::SignedApiAnnouncement;
33use fedimint_core::net::guardian_metadata::SignedGuardianMetadata;
34use fedimint_core::peer_availability::PeerAvailability;
35use fedimint_core::session_outcome::{SessionOutcome, SessionStatus};
36use fedimint_core::task::{MaybeSend, MaybeSync};
37use fedimint_core::transaction::{Transaction, TransactionSubmissionOutcome};
38use fedimint_core::util::backoff_util::api_networking_backoff;
39use fedimint_core::util::{FmtCompact as _, SafeUrl};
40use fedimint_core::{
41 ChainId, NumPeersExt, PeerId, TransactionId, apply, async_trait_maybe_send, dyn_newtype_define,
42 util,
43};
44use fedimint_logging::LOG_CLIENT_NET_API;
45use fedimint_metrics::HistogramExt as _;
46use futures::stream::{BoxStream, FuturesUnordered};
47use futures::{Future, StreamExt};
48use global_api::with_cache::GlobalFederationApiWithCache;
49use jsonrpsee_core::DeserializeOwned;
50use serde::{Deserialize, Serialize};
51use serde_json::Value;
52use tokio::sync::watch;
53use tokio_stream::wrappers::WatchStream;
54use tracing::{debug, instrument, trace, warn};
55
56use crate::metrics::{CLIENT_API_REQUEST_DURATION_SECONDS, CLIENT_API_REQUESTS_TOTAL};
57use crate::query::{QueryStep, QueryStrategy, ThresholdConsensus};
58
59pub const VERSION_THAT_INTRODUCED_GET_SESSION_STATUS_V2: ApiVersion = ApiVersion::new(0, 5);
60
61pub const VERSION_THAT_INTRODUCED_GET_SESSION_STATUS: ApiVersion =
62 ApiVersion { major: 0, minor: 1 };
63
64pub const VERSION_THAT_INTRODUCED_AWAIT_OUTPUTS_OUTCOMES: ApiVersion = ApiVersion::new(0, 8);
65pub type FederationResult<T> = Result<T, FederationError>;
66pub type SerdeOutputOutcome = SerdeModuleEncoding<DynOutputOutcome>;
67
68pub type OutputOutcomeResult<O> = result::Result<O, OutputOutcomeError>;
69
70#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable)]
74pub struct ApiVersionSet {
75 pub core: ApiVersion,
76 pub modules: BTreeMap<ModuleInstanceId, ApiVersion>,
77}
78
79#[apply(async_trait_maybe_send!)]
81pub trait IRawFederationApi: Debug + MaybeSend + MaybeSync {
82 fn all_peers(&self) -> &BTreeSet<PeerId>;
90
91 fn self_peer(&self) -> Option<PeerId>;
96
97 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi;
98
99 async fn request_raw(
101 &self,
102 peer_id: PeerId,
103 method: &str,
104 params: &ApiRequestErased,
105 ) -> ServerResult<Value>;
106
107 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>>;
122 async fn wait_for_initialized_connections(&self);
127
128 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection>;
134}
135
136#[apply(async_trait_maybe_send!)]
139pub trait FederationApiExt: IRawFederationApi {
140 async fn request_single_peer<Ret>(
141 &self,
142 method: String,
143 params: ApiRequestErased,
144 peer: PeerId,
145 ) -> ServerResult<Ret>
146 where
147 Ret: DeserializeOwned,
148 {
149 self.request_raw(peer, &method, ¶ms)
150 .await
151 .and_then(|v| {
152 serde_json::from_value(v)
153 .map_err(|e| ServerError::ResponseDeserialization(Box::new(e)))
154 })
155 }
156
157 async fn request_single_peer_federation<FedRet>(
158 &self,
159 method: String,
160 params: ApiRequestErased,
161 peer_id: PeerId,
162 ) -> FederationResult<FedRet>
163 where
164 FedRet: serde::de::DeserializeOwned + Eq + Debug + Clone + MaybeSend,
165 {
166 self.request_raw(peer_id, &method, ¶ms)
167 .await
168 .and_then(|v| {
169 serde_json::from_value(v)
170 .map_err(|e| ServerError::ResponseDeserialization(Box::new(e)))
171 })
172 .map_err(|e| error::FederationError::new_one_peer(peer_id, method, params, e))
173 }
174
175 #[instrument(target = LOG_CLIENT_NET_API, skip_all, fields(method=method))]
178 async fn request_with_strategy<PR: DeserializeOwned + MaybeSend, FR: Debug>(
179 &self,
180 mut strategy: impl QueryStrategy<PR, FR> + MaybeSend,
181 method: String,
182 params: ApiRequestErased,
183 ) -> FederationResult<FR> {
184 #[cfg(not(target_family = "wasm"))]
188 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _> + Send>>>::new();
189 #[cfg(target_family = "wasm")]
190 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _>>>>::new();
191
192 for peer in self.all_peers() {
193 futures.push(Box::pin({
194 let method = &method;
195 let params = ¶ms;
196 async move {
197 let result = self
198 .request_single_peer(method.clone(), params.clone(), *peer)
199 .await;
200
201 (*peer, result)
202 }
203 }));
204 }
205
206 let mut peer_errors = BTreeMap::new();
207 let peer_error_threshold = self.all_peers().to_num_peers().one_honest();
208
209 loop {
210 let (peer, result) = futures
211 .next()
212 .await
213 .expect("Query strategy ran out of peers to query without returning a result");
214
215 match result {
216 Ok(response) => match strategy.process(peer, response).await {
217 QueryStep::Retry(peers) => {
218 for peer in peers {
219 futures.push(Box::pin({
220 let method = &method;
221 let params = ¶ms;
222 async move {
223 let result = self
224 .request_single_peer(method.clone(), params.clone(), peer)
225 .await;
226
227 (peer, result)
228 }
229 }));
230 }
231 }
232 QueryStep::Success(response) => return Ok(response),
233 QueryStep::Failure(e) => {
234 peer_errors.insert(peer, e);
235 }
236 QueryStep::Continue => {}
237 },
238 Err(e) => {
239 e.report_if_unusual(peer, "RequestWithStrategy");
240
241 if let QueryStep::Success(response) = strategy.process_error(peer, &e) {
246 return Ok(response);
247 }
248
249 peer_errors.insert(peer, e);
250 }
251 }
252
253 if peer_errors.len() == peer_error_threshold {
254 return Err(FederationError::peer_errors(
255 method.clone(),
256 params.params.clone(),
257 peer_errors,
258 ));
259 }
260 }
261 }
262
263 #[instrument(target = LOG_CLIENT_NET_API, level = "debug", skip(self, strategy))]
264 async fn request_with_strategy_retry<PR: DeserializeOwned + MaybeSend, FR: Debug>(
265 &self,
266 mut strategy: impl QueryStrategy<PR, FR> + MaybeSend,
267 method: String,
268 params: ApiRequestErased,
269 ) -> FR {
270 #[cfg(not(target_family = "wasm"))]
274 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _> + Send>>>::new();
275 #[cfg(target_family = "wasm")]
276 let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output = _>>>>::new();
277
278 for peer in self.all_peers() {
279 futures.push(Box::pin({
280 let method = &method;
281 let params = ¶ms;
282 async move {
283 let response = util::retry(
284 format!("api-request-{method}-{peer}"),
285 api_networking_backoff(),
286 || async {
287 self.request_single_peer(method.clone(), params.clone(), *peer)
288 .await
289 .inspect_err(|e| {
290 e.report_if_unusual(*peer, "QueryWithStrategyRetry");
291 })
292 },
293 )
294 .await
295 .expect("Number of retries has no limit");
296
297 (*peer, response)
298 }
299 }));
300 }
301
302 loop {
303 let (peer, response) = match futures.next().await {
304 Some(t) => t,
305 None => pending().await,
306 };
307
308 match strategy.process(peer, response).await {
309 QueryStep::Retry(peers) => {
310 for peer in peers {
311 futures.push(Box::pin({
312 let method = &method;
313 let params = ¶ms;
314 async move {
315 let response = util::retry(
316 format!("api-request-{method}-{peer}"),
317 api_networking_backoff(),
318 || async {
319 self.request_single_peer(
320 method.clone(),
321 params.clone(),
322 peer,
323 )
324 .await
325 .inspect_err(|err| {
326 if err.is_unusual() {
327 debug!(target: LOG_CLIENT_NET_API, err = %err.fmt_compact(), "Unusual peer error");
328 }
329 })
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 FederationGeneralError::AdminPeerIdNotSet,
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 FederationGeneralError::AdminPeerIdNotSet,
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 ) -> Self {
451 GlobalFederationApiWithCache::new(FederationApi::new(connectors, peers, None, api_secret))
452 .into()
453 }
454 pub fn new_admin(
455 connectors: ConnectorRegistry,
456 peer: PeerId,
457 url: SafeUrl,
458 api_secret: Option<&str>,
459 ) -> DynGlobalApi {
460 GlobalFederationApiWithCache::new(FederationApi::new(
461 connectors,
462 [(peer, url)].into(),
463 Some(peer),
464 api_secret,
465 ))
466 .into()
467 }
468
469 pub fn new_admin_setup(connectors: ConnectorRegistry, url: SafeUrl) -> Self {
470 Self::new_admin(
473 connectors,
474 PeerId::from(1024),
475 url,
476 None,
478 )
479 }
480}
481
482#[apply(async_trait_maybe_send!)]
484pub trait IGlobalFederationApi: IRawFederationApi {
485 async fn submit_transaction(
486 &self,
487 tx: Transaction,
488 ) -> SerdeModuleEncoding<TransactionSubmissionOutcome>;
489
490 async fn await_block(
491 &self,
492 block_index: u64,
493 decoders: &ModuleDecoderRegistry,
494 ) -> FederationResult<SessionOutcome>;
495
496 async fn get_session_status(
497 &self,
498 block_index: u64,
499 decoders: &ModuleDecoderRegistry,
500 core_api_version: ApiVersion,
501 broadcast_public_keys: Option<&BTreeMap<PeerId, secp256k1::PublicKey>>,
502 ) -> FederationResult<SessionStatus>;
503
504 async fn session_count(&self) -> FederationResult<u64>;
505
506 async fn await_transaction(&self, txid: TransactionId) -> TransactionId;
507
508 async fn upload_backup(&self, request: &SignedBackupRequest) -> FederationResult<()>;
509
510 async fn download_backup(
511 &self,
512 id: &secp256k1::PublicKey,
513 ) -> FederationResult<BTreeMap<PeerId, Option<ClientBackupSnapshot>>>;
514
515 async fn setup_status(&self, auth: ApiAuth) -> FederationResult<SetupStatus>;
516
517 async fn set_local_params(
518 &self,
519 name: String,
520 federation_name: Option<String>,
521 disable_base_fees: Option<bool>,
522 enabled_modules: Option<BTreeSet<ModuleKind>>,
523 federation_size: Option<u32>,
524 auth: ApiAuth,
525 ) -> FederationResult<String>;
526
527 async fn add_peer_connection_info(
528 &self,
529 info: String,
530 auth: ApiAuth,
531 ) -> FederationResult<String>;
532
533 async fn reset_peer_setup_codes(&self, auth: ApiAuth) -> FederationResult<()>;
535
536 async fn get_setup_code(&self, auth: ApiAuth) -> FederationResult<Option<String>>;
538
539 async fn start_dkg(&self, auth: ApiAuth) -> FederationResult<()>;
543
544 async fn status(&self) -> FederationResult<StatusResponse>;
546
547 async fn audit(&self, auth: ApiAuth) -> FederationResult<AuditSummary>;
549
550 async fn guardian_config_backup(&self, auth: ApiAuth)
552 -> FederationResult<GuardianConfigBackup>;
553
554 async fn auth(&self, auth: ApiAuth) -> FederationResult<()>;
556
557 async fn restart_federation_setup(&self, auth: ApiAuth) -> FederationResult<()>;
558
559 async fn submit_api_announcement(
561 &self,
562 peer_id: PeerId,
563 announcement: SignedApiAnnouncement,
564 ) -> FederationResult<()>;
565
566 async fn api_announcements(
567 &self,
568 guardian: PeerId,
569 ) -> ServerResult<BTreeMap<PeerId, SignedApiAnnouncement>>;
570
571 async fn sign_api_announcement(
572 &self,
573 api_url: SafeUrl,
574 auth: ApiAuth,
575 ) -> FederationResult<SignedApiAnnouncement>;
576
577 async fn submit_guardian_metadata(
579 &self,
580 peer_id: PeerId,
581 metadata: SignedGuardianMetadata,
582 ) -> FederationResult<()>;
583
584 async fn guardian_metadata(
585 &self,
586 guardian: PeerId,
587 ) -> ServerResult<BTreeMap<PeerId, SignedGuardianMetadata>>;
588
589 async fn sign_guardian_metadata(
590 &self,
591 metadata: fedimint_core::net::guardian_metadata::GuardianMetadata,
592 auth: ApiAuth,
593 ) -> FederationResult<SignedGuardianMetadata>;
594
595 async fn shutdown(&self, session: Option<u64>, auth: ApiAuth) -> FederationResult<()>;
596
597 async fn fedimintd_version(&self, peer_id: PeerId) -> ServerResult<String>;
599
600 async fn peer_availability(&self, peer_id: PeerId) -> ServerResult<PeerAvailability>;
602
603 async fn backup_statistics(&self, auth: ApiAuth) -> FederationResult<BackupStatistics>;
605
606 async fn get_invite_code(&self, guardian: PeerId) -> ServerResult<InviteCode>;
609
610 async fn chain_id(&self) -> FederationResult<ChainId>;
613}
614
615pub fn deserialize_outcome<R>(
616 outcome: &SerdeOutputOutcome,
617 module_decoder: &Decoder,
618) -> OutputOutcomeResult<R>
619where
620 R: OutputOutcome + MaybeSend,
621{
622 let dyn_outcome = outcome.try_into_inner_known_module_kind(module_decoder)?;
623
624 let module_instance_id = dyn_outcome.module_instance_id();
625
626 dyn_outcome
627 .as_any()
628 .downcast_ref()
629 .cloned()
630 .ok_or(OutputOutcomeError::WrongOutcomeType {
631 module_instance_id,
632 expected_type: std::any::type_name::<R>(),
633 })
634}
635
636#[derive(Clone, Debug)]
647pub struct FederationApi {
648 peers: BTreeMap<PeerId, SafeUrl>,
650 peers_keys: BTreeSet<PeerId>,
652 admin_id: Option<PeerId>,
654 module_id: Option<ModuleInstanceId>,
656 api_secret: Option<String>,
658 connection_pool: ConnectionPool<dyn IGuardianConnection>,
660}
661
662impl FederationApi {
663 pub fn new(
664 connectors: ConnectorRegistry,
665 peers: BTreeMap<PeerId, SafeUrl>,
666 admin_peer_id: Option<PeerId>,
667 api_secret: Option<&str>,
668 ) -> Self {
669 Self {
670 peers_keys: peers.keys().copied().collect(),
671 peers,
672 admin_id: admin_peer_id,
673 module_id: None,
674 api_secret: api_secret.map(ToOwned::to_owned),
675 connection_pool: ConnectionPool::new(connectors),
676 }
677 }
678
679 async fn get_or_create_connection(
680 &self,
681 url: &SafeUrl,
682 api_secret: Option<&str>,
683 ) -> ServerResult<DynGuaridianConnection> {
684 self.connection_pool
685 .get_or_create_connection(url, api_secret, |url, api_secret, connectors| async move {
686 let conn = connectors
687 .connect_guardian(&url, api_secret.as_deref())
688 .await?;
689 Ok(conn)
690 })
691 .await
692 }
693
694 async fn request(
695 &self,
696 peer: PeerId,
697 method: ApiMethod,
698 request: ApiRequestErased,
699 ) -> ServerResult<Value> {
700 trace!(target: LOG_CLIENT_NET_API, %peer, %method, "Api request");
701 let url = self
702 .peers
703 .get(&peer)
704 .ok_or_else(|| ServerError::InvalidPeerId { peer_id: peer })?;
705 let conn = self
706 .get_or_create_connection(url, self.api_secret.as_deref())
707 .await?;
708
709 let method_str = method.to_string();
710 let peer_str = peer.to_string();
711 let timer = CLIENT_API_REQUEST_DURATION_SECONDS
712 .with_label_values(&[&method_str, &peer_str])
713 .start_timer_ext();
714
715 let res = conn.request(method.clone(), request).await;
716
717 timer.observe_duration();
718
719 let result_label = if res.is_ok() { "success" } else { "error" }.to_string();
720 CLIENT_API_REQUESTS_TOTAL
721 .with_label_values(&[&method_str, &peer_str, &result_label])
722 .inc();
723
724 trace!(target: LOG_CLIENT_NET_API, ?method, res_ok = res.is_ok(), "Api response");
725
726 res
727 }
728
729 pub fn get_active_connection_receiver(&self) -> watch::Receiver<BTreeSet<SafeUrl>> {
733 self.connection_pool.get_active_connection_receiver()
734 }
735}
736
737impl IModuleFederationApi for FederationApi {}
738
739#[apply(async_trait_maybe_send!)]
740impl IRawFederationApi for FederationApi {
741 fn all_peers(&self) -> &BTreeSet<PeerId> {
742 &self.peers_keys
743 }
744
745 fn self_peer(&self) -> Option<PeerId> {
746 self.admin_id
747 }
748
749 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
750 FederationApi {
751 api_secret: self.api_secret.clone(),
752 peers: self.peers.clone(),
753 peers_keys: self.peers_keys.clone(),
754 admin_id: self.admin_id,
755 module_id: Some(id),
756 connection_pool: self.connection_pool.clone(),
757 }
758 .into()
759 }
760
761 #[instrument(
762 target = LOG_CLIENT_NET_API,
763 skip_all,
764 fields(
765 peer_id = %peer_id,
766 method = %method,
767 params = %params.params,
768 )
769 )]
770 async fn request_raw(
771 &self,
772 peer_id: PeerId,
773 method: &str,
774 params: &ApiRequestErased,
775 ) -> ServerResult<Value> {
776 let method = match self.module_id {
777 Some(module_id) => ApiMethod::Module(module_id, method.to_string()),
778 None => ApiMethod::Core(method.to_string()),
779 };
780
781 self.request(peer_id, method, params.clone()).await
782 }
783
784 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>> {
785 let peers = self.peers.clone();
786 let pool = self.connection_pool.clone();
787 let active_rx = self.connection_pool.get_active_connection_receiver();
788
789 let membership_ticks = WatchStream::new(active_rx.clone()).map(|_| ());
795 let path_change_ticks =
796 WatchStream::new(self.connection_pool.connectivity_change_notifier()).map(|_| ());
797 let ticks = futures::stream::select(membership_ticks, path_change_ticks);
798
799 ticks
800 .map(move |()| {
801 let active_urls = active_rx.borrow().clone();
802 peers
803 .iter()
804 .map(|(peer_id, url)| {
805 let status = if active_urls.contains(url) {
806 match pool.connectivity(url) {
815 Connectivity::Unknown => PeerStatus::Disconnected,
816 connectivity => PeerStatus::Connected(connectivity),
817 }
818 } else {
819 PeerStatus::Disconnected
820 };
821 (*peer_id, status)
822 })
823 .collect()
824 })
825 .boxed()
826 }
827 async fn wait_for_initialized_connections(&self) {
828 self.connection_pool
829 .wait_for_initialized_connections()
830 .await;
831 }
832
833 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection> {
834 let url = self
835 .peers
836 .get(&peer_id)
837 .ok_or_else(|| ServerError::InvalidPeerId { peer_id })?;
838 self.get_or_create_connection(url, self.api_secret.as_deref())
839 .await
840 }
841}
842
843#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
845pub struct LegacyFederationStatus {
846 pub session_count: u64,
847 pub status_by_peer: BTreeMap<PeerId, LegacyPeerStatus>,
848 pub peers_online: u64,
849 pub peers_offline: u64,
850 pub peers_flagged: u64,
853 pub scheduled_shutdown: Option<u64>,
854}
855
856#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
857pub struct LegacyPeerStatus {
858 pub last_contribution: Option<u64>,
859 pub connection_status: LegacyP2PConnectionStatus,
860 pub flagged: bool,
863}
864
865#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
866#[serde(rename_all = "snake_case")]
867pub enum LegacyP2PConnectionStatus {
868 #[default]
869 Disconnected,
870 Connected,
871}
872
873#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
874pub struct StatusResponse {
875 pub server: ServerStatusLegacy,
876 pub federation: Option<LegacyFederationStatus>,
877}
878
879#[cfg(test)]
880mod tests;