1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Debug;
3use std::num::NonZeroUsize;
4use std::sync::Arc;
5
6use anyhow::{anyhow, format_err};
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, 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 ) -> anyhow::Result<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 self.request_current_consensus::<SerdeModuleEncoding<SessionOutcome>>(
134 AWAIT_SESSION_OUTCOME_ENDPOINT.to_string(),
135 ApiRequestErased::new(block_index),
136 )
137 .await?
138 .try_into_inner(decoders)
139 .map_err(|e| anyhow!(e.to_string()))
140 }
141
142 pub(crate) fn select_peers_for_status(&self) -> impl Iterator<Item = PeerId> + '_ {
143 let mut peers = self.all_peers().iter().copied().collect_vec();
144 peers.shuffle(&mut rand::thread_rng());
145 peers.into_iter()
146 }
147
148 pub(crate) async fn get_session_status_raw_v2(
149 &self,
150 block_index: u64,
151 broadcast_public_keys: &BTreeMap<PeerId, secp256k1::PublicKey>,
152 decoders: &ModuleDecoderRegistry,
153 ) -> anyhow::Result<SessionStatus> {
154 if block_index.is_multiple_of(100) {
155 debug!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v2");
156 } else {
157 trace!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v2");
158 }
159 let params = ApiRequestErased::new(block_index);
160 let mut last_error = None;
161 for peer_id in self.select_peers_for_status() {
163 match self
164 .request_single_peer_federation::<SerdeModuleEncodingBase64<SessionStatusV2>>(
165 SESSION_STATUS_V2_ENDPOINT.to_string(),
166 params.clone(),
167 peer_id,
168 )
169 .await
170 .map_err(anyhow::Error::from)
171 .and_then(|s| Ok(s.try_into_inner(decoders)?))
172 {
173 Ok(SessionStatusV2::Complete(signed_session_outcome)) => {
174 if signed_session_outcome.verify(broadcast_public_keys, block_index) {
175 return Ok(SessionStatus::Complete(
177 signed_session_outcome.session_outcome,
178 ));
179 }
180 last_error = Some(format_err!("Invalid signature"));
181 }
182 Ok(SessionStatusV2::Initial | SessionStatusV2::Pending(..)) => {
183 return self.get_session_status_raw(block_index, decoders).await;
185 }
186 Err(err) => {
187 last_error = Some(err);
188 }
189 }
190 assert!(last_error.is_some());
192 }
193 Err(last_error.expect("must have at least one peer"))
194 }
195
196 pub(crate) async fn get_session_status_raw(
197 &self,
198 block_index: u64,
199 decoders: &ModuleDecoderRegistry,
200 ) -> anyhow::Result<SessionStatus> {
201 if block_index.is_multiple_of(100) {
202 debug!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v1");
203 } else {
204 trace!(target: LOG_CLIENT_NET_API, block_index, "Get session status raw v1");
205 }
206 self.request_current_consensus::<SerdeModuleEncoding<SessionStatus>>(
207 SESSION_STATUS_ENDPOINT.to_string(),
208 ApiRequestErased::new(block_index),
209 )
210 .await?
211 .try_into_inner(&decoders.clone().with_fallback())
212 .map_err(|e| anyhow!(e))
213 }
214}
215
216#[apply(async_trait_maybe_send!)]
217impl<T> IRawFederationApi for GlobalFederationApiWithCache<T>
218where
219 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
220{
221 fn all_peers(&self) -> &BTreeSet<PeerId> {
222 self.inner.all_peers()
223 }
224
225 fn self_peer(&self) -> Option<PeerId> {
226 self.inner.self_peer()
227 }
228
229 fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
230 self.inner.with_module(id)
231 }
232
233 async fn request_raw(
235 &self,
236 peer_id: PeerId,
237 method: &str,
238 params: &ApiRequestErased,
239 ) -> ServerResult<Value> {
240 self.inner.request_raw(peer_id, method, params).await
241 }
242
243 fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, PeerStatus>> {
244 self.inner.connection_status_stream()
245 }
246
247 async fn wait_for_initialized_connections(&self) {
248 self.inner.wait_for_initialized_connections().await;
249 }
250
251 async fn get_peer_connection(&self, peer_id: PeerId) -> ServerResult<DynGuaridianConnection> {
252 self.inner.get_peer_connection(peer_id).await
253 }
254}
255
256#[apply(async_trait_maybe_send!)]
257impl<T> IGlobalFederationApi for GlobalFederationApiWithCache<T>
258where
259 T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
260{
261 async fn await_block(
262 &self,
263 session_idx: u64,
264 decoders: &ModuleDecoderRegistry,
265 ) -> anyhow::Result<SessionOutcome> {
266 let mut lru_lock = self.await_session_lru.lock().await;
267
268 let entry_arc = lru_lock
269 .get_or_insert(session_idx, || Arc::new(OnceCell::new()))
270 .clone();
271
272 drop(lru_lock);
274
275 entry_arc
276 .get_or_try_init(|| self.await_block_raw(session_idx, decoders))
277 .await
278 .cloned()
279 }
280
281 async fn get_session_status(
282 &self,
283 session_idx: u64,
284 decoders: &ModuleDecoderRegistry,
285 core_api_version: ApiVersion,
286 broadcast_public_keys: Option<&BTreeMap<PeerId, secp256k1::PublicKey>>,
287 ) -> anyhow::Result<SessionStatus> {
288 enum NoCacheErr {
289 Initial,
290 Pending(Vec<AcceptedItem>),
291 Err(anyhow::Error),
292 }
293
294 let mut lru_lock = self.get_session_status_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 match entry_arc
304 .get_or_try_init(|| async {
305 let session_status =
306 if core_api_version < VERSION_THAT_INTRODUCED_GET_SESSION_STATUS_V2 {
307 self.get_session_status_raw(session_idx, decoders).await
308 } else if let Some(broadcast_public_keys) = broadcast_public_keys {
309 self.get_session_status_raw_v2(session_idx, broadcast_public_keys, decoders)
310 .await
311 } else {
312 self.get_session_status_raw(session_idx, decoders).await
313 };
314 match session_status {
315 Err(e) => Err(NoCacheErr::Err(e)),
316 Ok(SessionStatus::Initial) => Err(NoCacheErr::Initial),
317 Ok(SessionStatus::Pending(s)) => Err(NoCacheErr::Pending(s)),
318 Ok(SessionStatus::Complete(s)) => Ok(s),
320 }
321 })
322 .await
323 .cloned()
324 {
325 Ok(s) => Ok(SessionStatus::Complete(s)),
326 Err(NoCacheErr::Initial) => Ok(SessionStatus::Initial),
327 Err(NoCacheErr::Pending(s)) => Ok(SessionStatus::Pending(s)),
328 Err(NoCacheErr::Err(e)) => Err(e),
329 }
330 }
331
332 async fn submit_transaction(
333 &self,
334 tx: Transaction,
335 ) -> SerdeModuleEncoding<TransactionSubmissionOutcome> {
336 self.request_current_consensus_retry(
337 SUBMIT_TRANSACTION_ENDPOINT.to_owned(),
338 ApiRequestErased::new(SerdeTransaction::from(&tx)),
339 )
340 .await
341 }
342
343 async fn session_count(&self) -> FederationResult<u64> {
344 self.request_current_consensus(
345 SESSION_COUNT_ENDPOINT.to_owned(),
346 ApiRequestErased::default(),
347 )
348 .await
349 }
350
351 async fn await_transaction(&self, txid: TransactionId) -> TransactionId {
352 self.request_current_consensus_retry(
353 AWAIT_TRANSACTION_ENDPOINT.to_owned(),
354 ApiRequestErased::new(txid),
355 )
356 .await
357 }
358
359 async fn upload_backup(&self, request: &SignedBackupRequest) -> FederationResult<()> {
360 self.request_current_consensus(BACKUP_ENDPOINT.to_owned(), ApiRequestErased::new(request))
361 .await
362 }
363
364 async fn download_backup(
365 &self,
366 id: &secp256k1::PublicKey,
367 ) -> FederationResult<BTreeMap<PeerId, Option<ClientBackupSnapshot>>> {
368 self.request_with_strategy(
369 FilterMapThreshold::new(|_, snapshot| Ok(snapshot), self.all_peers().to_num_peers()),
370 RECOVER_ENDPOINT.to_owned(),
371 ApiRequestErased::new(id),
372 )
373 .await
374 }
375
376 async fn setup_status(&self, auth: ApiAuth) -> FederationResult<SetupStatus> {
377 self.request_admin(SETUP_STATUS_ENDPOINT, ApiRequestErased::default(), auth)
378 .await
379 }
380
381 async fn set_local_params(
382 &self,
383 name: String,
384 federation_name: Option<String>,
385 disable_base_fees: Option<bool>,
386 enabled_modules: Option<BTreeSet<ModuleKind>>,
387 federation_size: Option<u32>,
388 auth: ApiAuth,
389 ) -> FederationResult<String> {
390 self.request_admin(
391 SET_LOCAL_PARAMS_ENDPOINT,
392 ApiRequestErased::new(SetLocalParamsRequest {
393 name,
394 federation_name,
395 disable_base_fees,
396 enabled_modules,
397 federation_size,
398 }),
399 auth,
400 )
401 .await
402 }
403
404 async fn add_peer_connection_info(
405 &self,
406 info: String,
407 auth: ApiAuth,
408 ) -> FederationResult<String> {
409 self.request_admin(
410 ADD_PEER_SETUP_CODE_ENDPOINT,
411 ApiRequestErased::new(info),
412 auth,
413 )
414 .await
415 }
416
417 async fn reset_peer_setup_codes(&self, auth: ApiAuth) -> FederationResult<()> {
418 self.request_admin(
419 RESET_PEER_SETUP_CODES_ENDPOINT,
420 ApiRequestErased::default(),
421 auth,
422 )
423 .await
424 }
425
426 async fn get_setup_code(&self, auth: ApiAuth) -> FederationResult<Option<String>> {
427 self.request_admin(GET_SETUP_CODE_ENDPOINT, ApiRequestErased::default(), auth)
428 .await
429 }
430
431 async fn start_dkg(&self, auth: ApiAuth) -> FederationResult<()> {
432 self.request_admin(START_DKG_ENDPOINT, ApiRequestErased::default(), auth)
433 .await
434 }
435
436 async fn status(&self) -> FederationResult<StatusResponse> {
437 self.request_admin_no_auth(STATUS_ENDPOINT, ApiRequestErased::default())
438 .await
439 }
440
441 async fn audit(&self, auth: ApiAuth) -> FederationResult<AuditSummary> {
442 self.request_admin(AUDIT_ENDPOINT, ApiRequestErased::default(), auth)
443 .await
444 }
445
446 async fn guardian_config_backup(
447 &self,
448 auth: ApiAuth,
449 ) -> FederationResult<GuardianConfigBackup> {
450 self.request_admin(
451 GUARDIAN_CONFIG_BACKUP_ENDPOINT,
452 ApiRequestErased::default(),
453 auth,
454 )
455 .await
456 }
457
458 async fn auth(&self, auth: ApiAuth) -> FederationResult<()> {
459 self.request_admin(AUTH_ENDPOINT, ApiRequestErased::default(), auth)
460 .await
461 }
462
463 async fn restart_federation_setup(&self, auth: ApiAuth) -> FederationResult<()> {
464 self.request_admin(
465 RESTART_FEDERATION_SETUP_ENDPOINT,
466 ApiRequestErased::default(),
467 auth,
468 )
469 .await
470 }
471
472 async fn submit_api_announcement(
473 &self,
474 announcement_peer_id: PeerId,
475 announcement: SignedApiAnnouncement,
476 ) -> FederationResult<()> {
477 let peer_errors = join_all(self.all_peers().iter().map(|&peer_id| {
478 let announcement_inner = announcement.clone();
479 async move {
480 (
481 peer_id,
482 self.request_single_peer::<()>(
483 SUBMIT_API_ANNOUNCEMENT_ENDPOINT.into(),
484 ApiRequestErased::new(SignedApiAnnouncementSubmission {
485 signed_api_announcement: announcement_inner,
486 peer_id: announcement_peer_id,
487 }),
488 peer_id,
489 )
490 .await,
491 )
492 }
493 }))
494 .await
495 .into_iter()
496 .filter_map(|(peer_id, result)| match result {
497 Ok(()) => None,
498 Err(e) => Some((peer_id, e)),
499 })
500 .collect::<BTreeMap<_, _>>();
501
502 if peer_errors.is_empty() {
503 Ok(())
504 } else {
505 Err(FederationError {
506 method: SUBMIT_API_ANNOUNCEMENT_ENDPOINT.to_string(),
507 params: serde_json::to_value(announcement).expect("can be serialized"),
508 general: None,
509 peer_errors,
510 })
511 }
512 }
513
514 async fn api_announcements(
515 &self,
516 guardian: PeerId,
517 ) -> ServerResult<BTreeMap<PeerId, SignedApiAnnouncement>> {
518 self.request_single_peer(
519 API_ANNOUNCEMENTS_ENDPOINT.to_owned(),
520 ApiRequestErased::default(),
521 guardian,
522 )
523 .await
524 }
525
526 async fn sign_api_announcement(
527 &self,
528 api_url: SafeUrl,
529 auth: ApiAuth,
530 ) -> FederationResult<SignedApiAnnouncement> {
531 self.request_admin(
532 SIGN_API_ANNOUNCEMENT_ENDPOINT,
533 ApiRequestErased::new(api_url),
534 auth,
535 )
536 .await
537 }
538
539 async fn submit_guardian_metadata(
540 &self,
541 announcement_peer_id: PeerId,
542 metadata: fedimint_core::net::guardian_metadata::SignedGuardianMetadata,
543 ) -> FederationResult<()> {
544 use fedimint_core::net::guardian_metadata::SignedGuardianMetadataSubmission;
545 let peer_errors = join_all(self.all_peers().iter().map(|&peer_id| {
546 let metadata_inner = metadata.clone();
547 async move {
548 (
549 peer_id,
550 self.request_single_peer::<()>(
551 SUBMIT_GUARDIAN_METADATA_ENDPOINT.into(),
552 ApiRequestErased::new(SignedGuardianMetadataSubmission {
553 signed_guardian_metadata: metadata_inner,
554 peer_id: announcement_peer_id,
555 }),
556 peer_id,
557 )
558 .await,
559 )
560 }
561 }))
562 .await
563 .into_iter()
564 .filter_map(|(peer_id, result)| match result {
565 Ok(()) => None,
566 Err(e) => Some((peer_id, e)),
567 })
568 .collect::<BTreeMap<_, _>>();
569
570 if peer_errors.is_empty() {
571 Ok(())
572 } else {
573 Err(FederationError {
574 method: SUBMIT_GUARDIAN_METADATA_ENDPOINT.to_string(),
575 params: serde_json::to_value(&metadata).expect("can be serialized"),
576 general: None,
577 peer_errors,
578 })
579 }
580 }
581
582 async fn guardian_metadata(
583 &self,
584 guardian: PeerId,
585 ) -> ServerResult<BTreeMap<PeerId, fedimint_core::net::guardian_metadata::SignedGuardianMetadata>>
586 {
587 self.request_single_peer(
588 GUARDIAN_METADATA_ENDPOINT.to_owned(),
589 ApiRequestErased::default(),
590 guardian,
591 )
592 .await
593 }
594
595 async fn sign_guardian_metadata(
596 &self,
597 metadata: fedimint_core::net::guardian_metadata::GuardianMetadata,
598 auth: ApiAuth,
599 ) -> FederationResult<fedimint_core::net::guardian_metadata::SignedGuardianMetadata> {
600 self.request_admin(
601 SIGN_GUARDIAN_METADATA_ENDPOINT,
602 ApiRequestErased::new(metadata),
603 auth,
604 )
605 .await
606 }
607
608 async fn shutdown(&self, session: Option<u64>, auth: ApiAuth) -> FederationResult<()> {
609 self.request_admin(SHUTDOWN_ENDPOINT, ApiRequestErased::new(session), auth)
610 .await
611 }
612
613 async fn backup_statistics(&self, auth: ApiAuth) -> FederationResult<BackupStatistics> {
614 self.request_admin(
615 BACKUP_STATISTICS_ENDPOINT,
616 ApiRequestErased::default(),
617 auth,
618 )
619 .await
620 }
621
622 async fn fedimintd_version(&self, peer_id: PeerId) -> ServerResult<String> {
623 self.request_single_peer(
624 FEDIMINTD_VERSION_ENDPOINT.to_owned(),
625 ApiRequestErased::default(),
626 peer_id,
627 )
628 .await
629 }
630
631 async fn get_invite_code(&self, guardian: PeerId) -> ServerResult<InviteCode> {
632 self.request_single_peer(
633 INVITE_CODE_ENDPOINT.to_owned(),
634 ApiRequestErased::default(),
635 guardian,
636 )
637 .await
638 }
639
640 async fn chain_id(&self) -> FederationResult<ChainId> {
641 self.request_current_consensus(CHAIN_ID_ENDPOINT.to_owned(), ApiRequestErased::default())
642 .await
643 }
644}