fedimint_api_client/
lib.rs
1#![deny(clippy::pedantic)]
2#![allow(clippy::missing_errors_doc)]
3#![allow(clippy::missing_panics_doc)]
4#![allow(clippy::module_name_repetitions)]
5#![allow(clippy::must_use_candidate)]
6#![allow(clippy::return_self_not_must_use)]
7
8use anyhow::{Context as _, bail};
9use api::net::Connector;
10use api::{DynGlobalApi, FederationApiExt as _, PeerError};
11use fedimint_core::config::{ClientConfig, FederationId};
12use fedimint_core::endpoint_constants::CLIENT_CONFIG_ENDPOINT;
13use fedimint_core::invite_code::InviteCode;
14use fedimint_core::module::ApiRequestErased;
15use fedimint_core::util::backoff_util;
16use fedimint_logging::LOG_CLIENT;
17use query::FilterMap;
18use tracing::debug;
19
20pub mod api;
21pub mod query;
23
24impl Connector {
25 pub async fn download_from_invite_code(
29 &self,
30 invite: &InviteCode,
31 ) -> anyhow::Result<ClientConfig> {
32 debug!(
33 target: LOG_CLIENT,
34 %invite,
35 peers = ?invite.peers(),
36 "Downloading client config via invite code"
37 );
38
39 let federation_id = invite.federation_id();
40 let api = DynGlobalApi::from_endpoints(invite.peers(), &invite.api_secret()).await?;
41 let api_secret = invite.api_secret();
42
43 fedimint_core::util::retry(
44 "Downloading client config",
45 backoff_util::aggressive_backoff(),
46 || self.try_download_client_config(&api, federation_id, api_secret.clone()),
47 )
48 .await
49 .context("Failed to download client config")
50 }
51
52 pub async fn try_download_client_config(
54 &self,
55 api: &DynGlobalApi,
56 federation_id: FederationId,
57 api_secret: Option<String>,
58 ) -> anyhow::Result<ClientConfig> {
59 debug!(target: LOG_CLIENT, "Downloading client config from peer");
60 let query_strategy = FilterMap::new(move |cfg: ClientConfig| {
62 if federation_id != cfg.global.calculate_federation_id() {
63 return Err(PeerError::ConditionFailed(anyhow::anyhow!(
64 "FederationId in invite code does not match client config"
65 )));
66 }
67
68 Ok(cfg.global.api_endpoints)
69 });
70
71 let api_endpoints = api
72 .request_with_strategy(
73 query_strategy,
74 CLIENT_CONFIG_ENDPOINT.to_owned(),
75 ApiRequestErased::default(),
76 )
77 .await?;
78
79 let api_endpoints = api_endpoints.into_iter().map(|(peer, url)| (peer, url.url));
81
82 debug!(target: LOG_CLIENT, "Verifying client config with all peers");
83
84 let client_config = DynGlobalApi::from_endpoints(api_endpoints, &api_secret)
85 .await?
86 .request_current_consensus::<ClientConfig>(
87 CLIENT_CONFIG_ENDPOINT.to_owned(),
88 ApiRequestErased::default(),
89 )
90 .await?;
91
92 if client_config.calculate_federation_id() != federation_id {
93 bail!("Obtained client config has different federation id");
94 }
95
96 Ok(client_config)
97 }
98}