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::anyhow;
12use bitcoin::secp256k1;
13pub use error::{
14 ClientConfigDownloadError, FederationError, FederationGeneralError, OutputOutcomeError,
15};
16pub use fedimint_connectors::ServerResult;
17pub use fedimint_connectors::error::ServerError;
18use fedimint_connectors::{
19 ConnectionPool, Connectivity, ConnectorRegistry, DynGuaridianConnection, IGuardianConnection,
20 PeerStatus,
21};
22use fedimint_core::admin_client::{GuardianConfigBackup, ServerStatusLegacy, SetupStatus};
23use fedimint_core::backup::{BackupStatistics, ClientBackupSnapshot};
24use fedimint_core::core::backup::SignedBackupRequest;
25use fedimint_core::core::{Decoder, DynOutputOutcome, ModuleInstanceId, ModuleKind, OutputOutcome};
26use fedimint_core::encoding::{Decodable, Encodable};
27use fedimint_core::invite_code::InviteCode;
28use fedimint_core::module::audit::AuditSummary;
29use fedimint_core::module::registry::ModuleDecoderRegistry;
30use fedimint_core::module::{
31 ApiAuth, ApiMethod, ApiRequestErased, ApiVersion, SerdeModuleEncoding,
32};
33use fedimint_core::net::api_announcement::SignedApiAnnouncement;
34use fedimint_core::net::guardian_metadata::SignedGuardianMetadata;
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, 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) {
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 .map_err(|e| anyhow!(e.to_string()))
293 },
294 )
295 .await
296 .expect("Number of retries has no limit");
297
298 (*peer, response)
299 }
300 }));
301 }
302
303 loop {
304 let (peer, response) = match futures.next().await {
305 Some(t) => t,
306 None => pending().await,
307 };
308
309 match strategy.process(peer, response) {
310 QueryStep::Retry(peers) => {
311 for peer in peers {
312 futures.push(Box::pin({
313 let method = &method;
314 let params = ¶ms;
315 async move {
316 let response = util::retry(
317 format!("api-request-{method}-{peer}"),
318 api_networking_backoff(),
319 || async {
320 self.request_single_peer(
321 method.clone(),
322 params.clone(),
323 peer,
324 )
325 .await
326 .inspect_err(|err| {
327 if err.is_unusual() {
328 debug!(target: LOG_CLIENT_NET_API, err = %err.fmt_compact(), "Unusual peer error");
329 }
330 })
331 .map_err(|e| anyhow!(e.to_string()))
332 },
333 )
334 .await
335 .expect("Number of retries has no limit");
336
337 (peer, response)
338 }
339 }));
340 }
341 }
342 QueryStep::Success(response) => return response,
343 QueryStep::Failure(e) => {
344 warn!(target: LOG_CLIENT_NET_API, "Query strategy returned non-retryable failure for peer {peer}: {e}");
345 }
346 QueryStep::Continue => {}
347 }
348 }
349 }
350
351 async fn request_current_consensus<Ret>(
352 &self,
353 method: String,
354 params: ApiRequestErased,
355 ) -> FederationResult<Ret>
356 where
357 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
358 {
359 self.request_with_strategy(
360 ThresholdConsensus::new(self.all_peers().to_num_peers()),
361 method,
362 params,
363 )
364 .await
365 }
366
367 async fn request_current_consensus_retry<Ret>(
368 &self,
369 method: String,
370 params: ApiRequestErased,
371 ) -> Ret
372 where
373 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
374 {
375 self.request_with_strategy_retry(
376 ThresholdConsensus::new(self.all_peers().to_num_peers()),
377 method,
378 params,
379 )
380 .await
381 }
382
383 async fn request_admin<Ret>(
384 &self,
385 method: &str,
386 params: ApiRequestErased,
387 auth: ApiAuth,
388 ) -> FederationResult<Ret>
389 where
390 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
391 {
392 let Some(self_peer_id) = self.self_peer() else {
393 return Err(FederationError::general(
394 method,
395 params,
396 FederationGeneralError::AdminPeerIdNotSet,
397 ));
398 };
399
400 self.request_single_peer_federation(method.into(), params.with_auth(auth), self_peer_id)
401 .await
402 }
403
404 async fn request_admin_no_auth<Ret>(
405 &self,
406 method: &str,
407 params: ApiRequestErased,
408 ) -> FederationResult<Ret>
409 where
410 Ret: DeserializeOwned + Eq + Debug + Clone + MaybeSend,
411 {
412 let Some(self_peer_id) = self.self_peer() else {
413 return Err(FederationError::general(
414 method,
415 params,
416 FederationGeneralError::AdminPeerIdNotSet,
417 ));
418 };
419
420 self.request_single_peer_federation(method.into(), params, self_peer_id)
421 .await
422 }
423}
424
425#[apply(async_trait_maybe_send!)]
426impl<T: ?Sized> FederationApiExt for T where T: IRawFederationApi {}
427
428pub trait IModuleFederationApi: IRawFederationApi {}
430
431dyn_newtype_define! {
432 #[derive(Clone)]
433 pub DynModuleApi(Arc<IModuleFederationApi>)
434}
435
436dyn_newtype_define! {
437 #[derive(Clone)]
438 pub DynGlobalApi(Arc<IGlobalFederationApi>)
439}
440
441impl AsRef<dyn IGlobalFederationApi + 'static> for DynGlobalApi {
442 fn as_ref(&self) -> &(dyn IGlobalFederationApi + 'static) {
443 self.inner.as_ref()
444 }
445}
446
447impl DynGlobalApi {
448 pub fn new(
449 connectors: ConnectorRegistry,
450 peers: BTreeMap<PeerId, SafeUrl>,
451 api_secret: Option<&str>,
452 ) -> Self {
453 GlobalFederationApiWithCache::new(FederationApi::new(connectors, peers, None, api_secret))
454 .into()
455 }
456 pub fn new_admin(
457 connectors: ConnectorRegistry,
458 peer: PeerId,
459 url: SafeUrl,
460 api_secret: Option<&str>,
461 ) -> DynGlobalApi {
462 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) -> 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 ) -> FederationResult<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 ) -> FederationResult<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.try_into_inner_known_module_kind(module_decoder)?;
622
623 let module_instance_id = dyn_outcome.module_instance_id();
624
625 dyn_outcome
626 .as_any()
627 .downcast_ref()
628 .cloned()
629 .ok_or(OutputOutcomeError::WrongOutcomeType {
630 module_instance_id,
631 expected_type: std::any::type_name::<R>(),
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
708 let method_str = method.to_string();
709 let peer_str = peer.to_string();
710 let timer = CLIENT_API_REQUEST_DURATION_SECONDS
711 .with_label_values(&[&method_str, &peer_str])
712 .start_timer_ext();
713
714 let res = conn.request(method.clone(), request).await;
715
716 timer.observe_duration();
717
718 let result_label = if res.is_ok() { "success" } else { "error" }.to_string();
719 CLIENT_API_REQUESTS_TOTAL
720 .with_label_values(&[&method_str, &peer_str, &result_label])
721 .inc();
722
723 trace!(target: LOG_CLIENT_NET_API, ?method, res_ok = res.is_ok(), "Api response");
724
725 res
726 }
727
728 pub fn get_active_connection_receiver(&self) -> watch::Receiver<BTreeSet<SafeUrl>> {
732 self.connection_pool.get_active_connection_receiver()
733 }
734}
735
736impl IModuleFederationApi for FederationApi {}
737
738#[apply(async_trait_maybe_send!)]
739impl IRawFederationApi for FederationApi {
740 fn all_peers(&self) -> &BTreeSet<PeerId> {
741 &self.peers_keys
742 }
743
744 fn self_peer(&self) -> Option<PeerId> {
745 self.admin_id
746 }
747
748 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
749 FederationApi {
750 api_secret: self.api_secret.clone(),
751 peers: self.peers.clone(),
752 peers_keys: self.peers_keys.clone(),
753 admin_id: self.admin_id,
754 module_id: Some(id),
755 connection_pool: self.connection_pool.clone(),
756 }
757 .into()
758 }
759
760 #[instrument(
761 target = LOG_CLIENT_NET_API,
762 skip_all,
763 fields(
764 peer_id = %peer_id,
765 method = %method,
766 params = %params.params,
767 )
768 )]
769 async fn request_raw(
770 &self,
771 peer_id: PeerId,
772 method: &str,
773 params: &ApiRequestErased,
774 ) -> ServerResult<Value> {
775 let method = match self.module_id {
776 Some(module_id) => ApiMethod::Module(module_id, method.to_string()),
777 None => ApiMethod::Core(method.to_string()),
778 };
779
780 self.request(peer_id, method, params.clone()).await
781 }
782
783 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>> {
784 let peers = self.peers.clone();
785 let pool = self.connection_pool.clone();
786 let active_rx = self.connection_pool.get_active_connection_receiver();
787
788 let membership_ticks = WatchStream::new(active_rx.clone()).map(|_| ());
794 let path_change_ticks =
795 WatchStream::new(self.connection_pool.connectivity_change_notifier()).map(|_| ());
796 let ticks = futures::stream::select(membership_ticks, path_change_ticks);
797
798 ticks
799 .map(move |()| {
800 let active_urls = active_rx.borrow().clone();
801 peers
802 .iter()
803 .map(|(peer_id, url)| {
804 let status = if active_urls.contains(url) {
805 match pool.connectivity(url) {
814 Connectivity::Unknown => PeerStatus::Disconnected,
815 connectivity => PeerStatus::Connected(connectivity),
816 }
817 } else {
818 PeerStatus::Disconnected
819 };
820 (*peer_id, status)
821 })
822 .collect()
823 })
824 .boxed()
825 }
826 async fn wait_for_initialized_connections(&self) {
827 self.connection_pool
828 .wait_for_initialized_connections()
829 .await;
830 }
831
832 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection> {
833 let url = self
834 .peers
835 .get(&peer_id)
836 .ok_or_else(|| ServerError::InvalidPeerId { peer_id })?;
837 self.get_or_create_connection(url, self.api_secret.as_deref())
838 .await
839 }
840}
841
842#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
844pub struct LegacyFederationStatus {
845 pub session_count: u64,
846 pub status_by_peer: BTreeMap<PeerId, LegacyPeerStatus>,
847 pub peers_online: u64,
848 pub peers_offline: u64,
849 pub peers_flagged: u64,
852 pub scheduled_shutdown: Option<u64>,
853}
854
855#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
856pub struct LegacyPeerStatus {
857 pub last_contribution: Option<u64>,
858 pub connection_status: LegacyP2PConnectionStatus,
859 pub flagged: bool,
862}
863
864#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
865#[serde(rename_all = "snake_case")]
866pub enum LegacyP2PConnectionStatus {
867 #[default]
868 Disconnected,
869 Connected,
870}
871
872#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
873pub struct StatusResponse {
874 pub server: ServerStatusLegacy,
875 pub federation: Option<LegacyFederationStatus>,
876}
877
878#[cfg(test)]
879mod tests;