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