1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Debug;
3use std::future::ready;
4use std::num::NonZeroUsize;
5use std::sync::Arc;
6
7use bitcoin::secp256k1;
8use fedimint_connectors::{DynGuaridianConnection, PeerStatus, ServerResult};
9use fedimint_core::admin_client::{GuardianConfigBackup, SetLocalParamsRequest, SetupStatus};
10use fedimint_core::backup::{BackupStatistics, ClientBackupSnapshot};
11use fedimint_core::core::backup::SignedBackupRequest;
12use fedimint_core::core::{ModuleInstanceId, ModuleKind};
13use fedimint_core::endpoint_constants::{
14 ADD_PEER_SETUP_CODE_ENDPOINT, API_ANNOUNCEMENTS_ENDPOINT, AUDIT_ENDPOINT, AUTH_ENDPOINT,
15 AWAIT_SESSION_OUTCOME_ENDPOINT, AWAIT_TRANSACTION_ENDPOINT, BACKUP_ENDPOINT,
16 BACKUP_STATISTICS_ENDPOINT, CHAIN_ID_ENDPOINT, FEDIMINTD_VERSION_ENDPOINT,
17 GET_SETUP_CODE_ENDPOINT, GUARDIAN_CONFIG_BACKUP_ENDPOINT, GUARDIAN_METADATA_ENDPOINT,
18 INVITE_CODE_ENDPOINT, RECOVER_ENDPOINT, RESET_PEER_SETUP_CODES_ENDPOINT,
19 RESTART_FEDERATION_SETUP_ENDPOINT, SESSION_COUNT_ENDPOINT, SESSION_STATUS_ENDPOINT,
20 SESSION_STATUS_V2_ENDPOINT, SET_LOCAL_PARAMS_ENDPOINT, SETUP_STATUS_ENDPOINT,
21 SHUTDOWN_ENDPOINT, SIGN_API_ANNOUNCEMENT_ENDPOINT, SIGN_GUARDIAN_METADATA_ENDPOINT,
22 START_DKG_ENDPOINT, STATUS_ENDPOINT, SUBMIT_API_ANNOUNCEMENT_ENDPOINT,
23 SUBMIT_GUARDIAN_METADATA_ENDPOINT, SUBMIT_TRANSACTION_ENDPOINT,
24};
25use fedimint_core::invite_code::InviteCode;
26use fedimint_core::module::audit::AuditSummary;
27use fedimint_core::module::registry::ModuleDecoderRegistry;
28use fedimint_core::module::{
29 ApiAuth, ApiRequestErased, ApiVersion, SerdeModuleEncoding, SerdeModuleEncodingBase64,
30};
31use fedimint_core::net::api_announcement::{
32 SignedApiAnnouncement, SignedApiAnnouncementSubmission,
33};
34use fedimint_core::session_outcome::{
35 AcceptedItem, SessionOutcome, SessionStatus, SessionStatusV2,
36};
37use fedimint_core::task::{MaybeSend, MaybeSync};
38use fedimint_core::transaction::{SerdeTransaction, Transaction, TransactionSubmissionOutcome};
39use fedimint_core::util::SafeUrl;
40use fedimint_core::{ChainId, NumPeersExt, PeerId, TransactionId, apply, async_trait_maybe_send};
41use fedimint_logging::LOG_CLIENT_NET_API;
42use futures::future::join_all;
43use futures::stream::BoxStream;
44use itertools::Itertools;
45use rand::seq::SliceRandom;
46use serde_json::Value;
47use tokio::sync::OnceCell;
48use tracing::{debug, trace};
49
50use super::super::{DynModuleApi, IGlobalFederationApi, IRawFederationApi, StatusResponse};
51use crate::api::{
52 FederationApiExt, FederationError, FederationGeneralError, FederationResult,
53 VERSION_THAT_INTRODUCED_GET_SESSION_STATUS_V2,
54};
55use crate::query::FilterMapThreshold;
56
57pub trait GlobalFederationApiWithCacheExt
60where
61 Self: Sized,
62{
63 fn with_cache(self) -> GlobalFederationApiWithCache<Self>;
64}
65
66impl<T> GlobalFederationApiWithCacheExt for T
67where
68 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
69{
70 fn with_cache(self) -> GlobalFederationApiWithCache<T> {
71 GlobalFederationApiWithCache::new(self)
72 }
73}
74
75#[derive(Debug)]
80pub struct GlobalFederationApiWithCache<T> {
81 pub(crate) inner: T,
82 pub(crate) await_session_lru:
92 Arc<tokio::sync::Mutex<lru::LruCache<u64, Arc<OnceCell<SessionOutcome>>>>>,
93
94 pub(crate) get_session_status_lru:
102 Arc<tokio::sync::Mutex<lru::LruCache<u64, Arc<OnceCell<SessionOutcome>>>>>,
103}
104
105impl<T> GlobalFederationApiWithCache<T> {
106 pub fn new(inner: T) -> GlobalFederationApiWithCache<T> {
107 Self {
108 inner,
109 await_session_lru: Arc::new(tokio::sync::Mutex::new(lru::LruCache::new(
110 NonZeroUsize::new(512).expect("is non-zero"),
111 ))),
112 get_session_status_lru: Arc::new(tokio::sync::Mutex::new(lru::LruCache::new(
113 NonZeroUsize::new(512).expect("is non-zero"),
114 ))),
115 }
116 }
117}
118
119impl<T> GlobalFederationApiWithCache<T>
120where
121 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
122{
123 pub(crate) async fn await_block_raw(
124 &self,
125 block_index: u64,
126 decoders: &ModuleDecoderRegistry,
127 ) -> FederationResult<SessionOutcome> {
128 if block_index.is_multiple_of(100) {
129 debug!(target: LOG_CLIENT_NET_API, block_index, "Awaiting block's outcome from Federation");
130 } else {
131 trace!(target: LOG_CLIENT_NET_API, block_index, "Awaiting block's outcome from Federation");
132 }
133 let response = self
134 .request_current_consensus::<SerdeModuleEncoding<SessionOutcome>>(
135 AWAIT_SESSION_OUTCOME_ENDPOINT.to_string(),
136 ApiRequestErased::new(block_index),
137 )
138 .await?;
139
140 response.try_into_inner(decoders).map_err(|e| {
141 FederationError::general(
142 AWAIT_SESSION_OUTCOME_ENDPOINT.to_string(),
143 ApiRequestErased::new(block_index),
144 FederationGeneralError::Decode(e),
145 )
146 })
147 }
148
149 pub(crate) fn select_peers_for_status(&self) -> impl Iterator<Item = PeerId> + '_ {
150 let mut peers = self.all_peers().iter().copied().collect_vec();
151 peers.shuffle(&mut rand::thread_rng());
152 peers.into_iter()
153 }
154
155 pub(crate) async fn get_session_status_raw_v2(
156 &self,
157 block_index: u64,
158 broadcast_public_keys: &BTreeMap<PeerId, secp256k1::PublicKey>,
159 decoders: &ModuleDecoderRegistry,
160 ) -> FederationResult<SessionStatus> {
161 if block_index.is_multiple_of(100) {
162 debug!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v2");
163 } else {
164 trace!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v2");
165 }
166 let params = ApiRequestErased::new(block_index);
167 let mut last_error = None;
168 for peer_id in self.select_peers_for_status() {
170 let decoded = self
171 .request_single_peer_federation::<SerdeModuleEncodingBase64<SessionStatusV2>>(
172 SESSION_STATUS_V2_ENDPOINT.to_string(),
173 params.clone(),
174 peer_id,
175 )
176 .await
177 .and_then(|s| {
178 s.try_into_inner(decoders).map_err(|e| {
179 FederationError::general(
180 SESSION_STATUS_V2_ENDPOINT.to_string(),
181 params.clone(),
182 FederationGeneralError::Decode(e),
183 )
184 })
185 });
186
187 match decoded {
188 Ok(SessionStatusV2::Complete(signed_session_outcome)) => {
189 if signed_session_outcome.verify(broadcast_public_keys, block_index) {
190 return Ok(SessionStatus::Complete(
192 signed_session_outcome.session_outcome,
193 ));
194 }
195 last_error = Some(FederationError::general(
196 SESSION_STATUS_V2_ENDPOINT.to_string(),
197 params.clone(),
198 FederationGeneralError::InvalidSignature,
199 ));
200 }
201 Ok(SessionStatusV2::Initial | SessionStatusV2::Pending(..)) => {
202 return self.get_session_status_raw(block_index, decoders).await;
204 }
205 Err(err) => {
206 last_error = Some(err);
207 }
208 }
209 assert!(last_error.is_some());
211 }
212 Err(last_error.expect("must have at least one peer"))
213 }
214
215 pub(crate) async fn get_session_status_raw(
216 &self,
217 block_index: u64,
218 decoders: &ModuleDecoderRegistry,
219 ) -> FederationResult<SessionStatus> {
220 if block_index.is_multiple_of(100) {
221 debug!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v1");
222 } else {
223 trace!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v1");
224 }
225 let response = self
226 .request_current_consensus::<SerdeModuleEncoding<SessionStatus>>(
227 SESSION_STATUS_ENDPOINT.to_string(),
228 ApiRequestErased::new(block_index),
229 )
230 .await?;
231
232 response
233 .try_into_inner(&decoders.clone().with_fallback())
234 .map_err(|e| {
235 FederationError::general(
236 SESSION_STATUS_ENDPOINT.to_string(),
237 ApiRequestErased::new(block_index),
238 FederationGeneralError::Decode(e),
239 )
240 })
241 }
242}
243
244#[apply(async_trait_maybe_send!)]
245impl<T> IRawFederationApi for GlobalFederationApiWithCache<T>
246where
247 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
248{
249 fn all_peers(&self) -> &BTreeSet<PeerId> {
250 self.inner.all_peers()
251 }
252
253 fn self_peer(&self) -> Option<PeerId> {
254 self.inner.self_peer()
255 }
256
257 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
258 self.inner.with_module(id)
259 }
260
261 async fn request_raw(
263 &self,
264 peer_id: PeerId,
265 method: &str,
266 params: &ApiRequestErased,
267 ) -> ServerResult<Value> {
268 self.inner.request_raw(peer_id, method, params).await
269 }
270
271 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>> {
272 self.inner.connection_status_stream()
273 }
274
275 async fn wait_for_initialized_connections(&self) {
276 self.inner.wait_for_initialized_connections().await;
277 }
278
279 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection> {
280 self.inner.get_peer_connection(peer_id).await
281 }
282}
283
284#[apply(async_trait_maybe_send!)]
285impl<T> IGlobalFederationApi for GlobalFederationApiWithCache<T>
286where
287 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
288{
289 async fn await_block(
290 &self,
291 session_idx: u64,
292 decoders: &ModuleDecoderRegistry,
293 ) -> FederationResult<SessionOutcome> {
294 let mut lru_lock = self.await_session_lru.lock().await;
295
296 let entry_arc = lru_lock
297 .get_or_insert(session_idx, || Arc::new(OnceCell::new()))
298 .clone();
299
300 drop(lru_lock);
302
303 entry_arc
304 .get_or_try_init(|| self.await_block_raw(session_idx, decoders))
305 .await
306 .cloned()
307 }
308
309 async fn get_session_status(
310 &self,
311 session_idx: u64,
312 decoders: &ModuleDecoderRegistry,
313 core_api_version: ApiVersion,
314 broadcast_public_keys: Option<&BTreeMap<PeerId, secp256k1::PublicKey>>,
315 ) -> FederationResult<SessionStatus> {
316 enum NoCacheErr {
317 Initial,
318 Pending(Vec<AcceptedItem>),
319 Err(FederationError),
320 }
321
322 let mut lru_lock = self.get_session_status_lru.lock().await;
323
324 let entry_arc = lru_lock
325 .get_or_insert(session_idx, || Arc::new(OnceCell::new()))
326 .clone();
327
328 drop(lru_lock);
330
331 match entry_arc
332 .get_or_try_init(|| async {
333 let session_status =
334 if core_api_version < VERSION_THAT_INTRODUCED_GET_SESSION_STATUS_V2 {
335 self.get_session_status_raw(session_idx, decoders).await
336 } else if let Some(broadcast_public_keys) = broadcast_public_keys {
337 self.get_session_status_raw_v2(session_idx, broadcast_public_keys, decoders)
338 .await
339 } else {
340 self.get_session_status_raw(session_idx, decoders).await
341 };
342 match session_status {
343 Err(e) => Err(NoCacheErr::Err(e)),
344 Ok(SessionStatus::Initial) => Err(NoCacheErr::Initial),
345 Ok(SessionStatus::Pending(s)) => Err(NoCacheErr::Pending(s)),
346 Ok(SessionStatus::Complete(s)) => Ok(s),
348 }
349 })
350 .await
351 .cloned()
352 {
353 Ok(s) => Ok(SessionStatus::Complete(s)),
354 Err(NoCacheErr::Initial) => Ok(SessionStatus::Initial),
355 Err(NoCacheErr::Pending(s)) => Ok(SessionStatus::Pending(s)),
356 Err(NoCacheErr::Err(e)) => Err(e),
357 }
358 }
359
360 async fn submit_transaction(
361 &self,
362 tx: Transaction,
363 ) -> SerdeModuleEncoding<TransactionSubmissionOutcome> {
364 self.request_current_consensus_retry(
365 SUBMIT_TRANSACTION_ENDPOINT.to_owned(),
366 ApiRequestErased::new(SerdeTransaction::from(&tx)),
367 )
368 .await
369 }
370
371 async fn session_count(&self) -> FederationResult<u64> {
372 self.request_current_consensus(
373 SESSION_COUNT_ENDPOINT.to_owned(),
374 ApiRequestErased::default(),
375 )
376 .await
377 }
378
379 async fn await_transaction(&self, txid: TransactionId) -> TransactionId {
380 self.request_current_consensus_retry(
381 AWAIT_TRANSACTION_ENDPOINT.to_owned(),
382 ApiRequestErased::new(txid),
383 )
384 .await
385 }
386
387 async fn upload_backup(&self, request: &SignedBackupRequest) -> FederationResult<()> {
388 self.request_current_consensus(BACKUP_ENDPOINT.to_owned(), ApiRequestErased::new(request))
389 .await
390 }
391
392 async fn download_backup(
393 &self,
394 id: &secp256k1::PublicKey,
395 ) -> FederationResult<BTreeMap<PeerId, Option<ClientBackupSnapshot>>> {
396 self.request_with_strategy(
397 FilterMapThreshold::new(
398 |_, snapshot| ready(Ok(snapshot)),
399 self.all_peers().to_num_peers(),
400 ),
401 RECOVER_ENDPOINT.to_owned(),
402 ApiRequestErased::new(id),
403 )
404 .await
405 }
406
407 async fn setup_status(&self, auth: ApiAuth) -> FederationResult<SetupStatus> {
408 self.request_admin(SETUP_STATUS_ENDPOINT, ApiRequestErased::default(), auth)
409 .await
410 }
411
412 async fn set_local_params(
413 &self,
414 name: String,
415 federation_name: Option<String>,
416 disable_base_fees: Option<bool>,
417 enabled_modules: Option<BTreeSet<ModuleKind>>,
418 federation_size: Option<u32>,
419 auth: ApiAuth,
420 ) -> FederationResult<String> {
421 self.request_admin(
422 SET_LOCAL_PARAMS_ENDPOINT,
423 ApiRequestErased::new(SetLocalParamsRequest {
424 name,
425 federation_name,
426 disable_base_fees,
427 enabled_modules,
428 federation_size,
429 }),
430 auth,
431 )
432 .await
433 }
434
435 async fn add_peer_connection_info(
436 &self,
437 info: String,
438 auth: ApiAuth,
439 ) -> FederationResult<String> {
440 self.request_admin(
441 ADD_PEER_SETUP_CODE_ENDPOINT,
442 ApiRequestErased::new(info),
443 auth,
444 )
445 .await
446 }
447
448 async fn reset_peer_setup_codes(&self, auth: ApiAuth) -> FederationResult<()> {
449 self.request_admin(
450 RESET_PEER_SETUP_CODES_ENDPOINT,
451 ApiRequestErased::default(),
452 auth,
453 )
454 .await
455 }
456
457 async fn get_setup_code(&self, auth: ApiAuth) -> FederationResult<Option<String>> {
458 self.request_admin(GET_SETUP_CODE_ENDPOINT, ApiRequestErased::default(), auth)
459 .await
460 }
461
462 async fn start_dkg(&self, auth: ApiAuth) -> FederationResult<()> {
463 self.request_admin(START_DKG_ENDPOINT, ApiRequestErased::default(), auth)
464 .await
465 }
466
467 async fn status(&self) -> FederationResult<StatusResponse> {
468 self.request_admin_no_auth(STATUS_ENDPOINT, ApiRequestErased::default())
469 .await
470 }
471
472 async fn audit(&self, auth: ApiAuth) -> FederationResult<AuditSummary> {
473 self.request_admin(AUDIT_ENDPOINT, ApiRequestErased::default(), auth)
474 .await
475 }
476
477 async fn guardian_config_backup(
478 &self,
479 auth: ApiAuth,
480 ) -> FederationResult<GuardianConfigBackup> {
481 self.request_admin(
482 GUARDIAN_CONFIG_BACKUP_ENDPOINT,
483 ApiRequestErased::default(),
484 auth,
485 )
486 .await
487 }
488
489 async fn auth(&self, auth: ApiAuth) -> FederationResult<()> {
490 self.request_admin(AUTH_ENDPOINT, ApiRequestErased::default(), auth)
491 .await
492 }
493
494 async fn restart_federation_setup(&self, auth: ApiAuth) -> FederationResult<()> {
495 self.request_admin(
496 RESTART_FEDERATION_SETUP_ENDPOINT,
497 ApiRequestErased::default(),
498 auth,
499 )
500 .await
501 }
502
503 async fn submit_api_announcement(
504 &self,
505 announcement_peer_id: PeerId,
506 announcement: SignedApiAnnouncement,
507 ) -> FederationResult<()> {
508 let peer_errors = join_all(self.all_peers().iter().map(|&peer_id| {
509 let announcement_inner = announcement.clone();
510 async move {
511 (
512 peer_id,
513 self.request_single_peer::<()>(
514 SUBMIT_API_ANNOUNCEMENT_ENDPOINT.into(),
515 ApiRequestErased::new(SignedApiAnnouncementSubmission {
516 signed_api_announcement: announcement_inner,
517 peer_id: announcement_peer_id,
518 }),
519 peer_id,
520 )
521 .await,
522 )
523 }
524 }))
525 .await
526 .into_iter()
527 .filter_map(|(peer_id, result)| match result {
528 Ok(()) => None,
529 Err(e) => Some((peer_id, e)),
530 })
531 .collect::<BTreeMap<_, _>>();
532
533 if peer_errors.is_empty() {
534 Ok(())
535 } else {
536 Err(FederationError {
537 method: SUBMIT_API_ANNOUNCEMENT_ENDPOINT.to_string(),
538 params: serde_json::to_value(announcement).expect("can be serialized"),
539 general: None,
540 peer_errors,
541 })
542 }
543 }
544
545 async fn api_announcements(
546 &self,
547 guardian: PeerId,
548 ) -> ServerResult<BTreeMap<PeerId, SignedApiAnnouncement>> {
549 self.request_single_peer(
550 API_ANNOUNCEMENTS_ENDPOINT.to_owned(),
551 ApiRequestErased::default(),
552 guardian,
553 )
554 .await
555 }
556
557 async fn sign_api_announcement(
558 &self,
559 api_url: SafeUrl,
560 auth: ApiAuth,
561 ) -> FederationResult<SignedApiAnnouncement> {
562 self.request_admin(
563 SIGN_API_ANNOUNCEMENT_ENDPOINT,
564 ApiRequestErased::new(api_url),
565 auth,
566 )
567 .await
568 }
569
570 async fn submit_guardian_metadata(
571 &self,
572 announcement_peer_id: PeerId,
573 metadata: fedimint_core::net::guardian_metadata::SignedGuardianMetadata,
574 ) -> FederationResult<()> {
575 use fedimint_core::net::guardian_metadata::SignedGuardianMetadataSubmission;
576 let peer_errors = join_all(self.all_peers().iter().map(|&peer_id| {
577 let metadata_inner = metadata.clone();
578 async move {
579 (
580 peer_id,
581 self.request_single_peer::<()>(
582 SUBMIT_GUARDIAN_METADATA_ENDPOINT.into(),
583 ApiRequestErased::new(SignedGuardianMetadataSubmission {
584 signed_guardian_metadata: metadata_inner,
585 peer_id: announcement_peer_id,
586 }),
587 peer_id,
588 )
589 .await,
590 )
591 }
592 }))
593 .await
594 .into_iter()
595 .filter_map(|(peer_id, result)| match result {
596 Ok(()) => None,
597 Err(e) => Some((peer_id, e)),
598 })
599 .collect::<BTreeMap<_, _>>();
600
601 if peer_errors.is_empty() {
602 Ok(())
603 } else {
604 Err(FederationError {
605 method: SUBMIT_GUARDIAN_METADATA_ENDPOINT.to_string(),
606 params: serde_json::to_value(&metadata).expect("can be serialized"),
607 general: None,
608 peer_errors,
609 })
610 }
611 }
612
613 async fn guardian_metadata(
614 &self,
615 guardian: PeerId,
616 ) -> ServerResult<BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata>>
617 {
618 self.request_single_peer(
619 GUARDIAN_METADATA_ENDPOINT.to_owned(),
620 ApiRequestErased::default(),
621 guardian,
622 )
623 .await
624 }
625
626 async fn sign_guardian_metadata(
627 &self,
628 metadata: fedimint_core::net::guardian_metadata::GuardianMetadata,
629 auth: ApiAuth,
630 ) -> FederationResult<fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
631 self.request_admin(
632 SIGN_GUARDIAN_METADATA_ENDPOINT,
633 ApiRequestErased::new(metadata),
634 auth,
635 )
636 .await
637 }
638
639 async fn shutdown(&self, session: Option<u64>, auth: ApiAuth) -> FederationResult<()> {
640 self.request_admin(SHUTDOWN_ENDPOINT, ApiRequestErased::new(session), auth)
641 .await
642 }
643
644 async fn backup_statistics(&self, auth: ApiAuth) -> FederationResult<BackupStatistics> {
645 self.request_admin(
646 BACKUP_STATISTICS_ENDPOINT,
647 ApiRequestErased::default(),
648 auth,
649 )
650 .await
651 }
652
653 async fn fedimintd_version(&self, peer_id: PeerId) -> ServerResult<String> {
654 self.request_single_peer(
655 FEDIMINTD_VERSION_ENDPOINT.to_owned(),
656 ApiRequestErased::default(),
657 peer_id,
658 )
659 .await
660 }
661
662 async fn get_invite_code(&self, guardian: PeerId) -> ServerResult<InviteCode> {
663 self.request_single_peer(
664 INVITE_CODE_ENDPOINT.to_owned(),
665 ApiRequestErased::default(),
666 guardian,
667 )
668 .await
669 }
670
671 async fn chain_id(&self) -> FederationResult<ChainId> {
672 self.request_current_consensus(CHAIN_ID_ENDPOINT.to_owned(), ApiRequestErased::default())
673 .await
674 }
675}