fedimint_client_module/
meta.rs
1use std::collections::BTreeMap;
2use std::time::{Duration, SystemTime};
3
4use anyhow::{Context as _, bail};
5use fedimint_api_client::api::DynGlobalApi;
6use fedimint_core::config::ClientConfig;
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::task::{MaybeSend, MaybeSync};
9use fedimint_core::util::{backoff_util, retry};
10use fedimint_core::{apply, async_trait_maybe_send};
11use fedimint_logging::LOG_CLIENT;
12use serde::{Deserialize, Serialize};
13use tracing::{debug, warn};
14
15#[apply(async_trait_maybe_send!)]
16pub trait MetaSource: MaybeSend + MaybeSync + 'static {
17 async fn wait_for_update(&self);
19 async fn fetch(
20 &self,
21 client_config: &ClientConfig,
22 api: &DynGlobalApi,
23 fetch_kind: FetchKind,
24 last_revision: Option<u64>,
25 ) -> anyhow::Result<MetaValues>;
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum FetchKind {
30 Initial,
33 Background,
35}
36
37#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38pub struct MetaValues {
39 pub values: BTreeMap<MetaFieldKey, MetaFieldValue>,
40 pub revision: u64,
41}
42
43#[derive(Debug, Clone, Copy)]
44pub struct MetaValue<T> {
45 pub fetch_time: SystemTime,
46 pub value: Option<T>,
47}
48
49#[derive(Clone, Debug, Default)]
52#[non_exhaustive]
53pub struct LegacyMetaSource {
54 reqwest: reqwest::Client,
55}
56
57#[apply(async_trait_maybe_send!)]
58impl MetaSource for LegacyMetaSource {
59 async fn wait_for_update(&self) {
60 fedimint_core::runtime::sleep(Duration::from_secs(10 * 60)).await;
61 }
62
63 async fn fetch(
64 &self,
65 client_config: &ClientConfig,
66 _api: &DynGlobalApi,
67 fetch_kind: FetchKind,
68 last_revision: Option<u64>,
69 ) -> anyhow::Result<MetaValues> {
70 let config_iter = client_config
71 .global
72 .meta
73 .iter()
74 .map(|(key, value)| (MetaFieldKey(key.clone()), MetaFieldValue(value.clone())));
75 let backoff = match fetch_kind {
76 FetchKind::Initial => backoff_util::aggressive_backoff(),
78 FetchKind::Background => backoff_util::background_backoff(),
79 };
80 let overrides = retry("fetch_meta_overrides", backoff, || {
81 fetch_meta_overrides(&self.reqwest, client_config, "meta_override_url")
82 })
83 .await?;
84 Ok(MetaValues {
85 values: config_iter.chain(overrides).collect(),
86 revision: last_revision.map_or(0, |r| r + 1),
87 })
88 }
89}
90
91#[derive(
92 Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
93)]
94pub struct MetaFieldKey(pub String);
95
96#[derive(Encodable, Decodable, Debug, Clone, Serialize, Deserialize)]
97pub struct MetaFieldValue(pub String);
98
99pub async fn fetch_meta_overrides(
100 reqwest: &reqwest::Client,
101 client_config: &ClientConfig,
102 field_name: &str,
103) -> anyhow::Result<BTreeMap<MetaFieldKey, MetaFieldValue>> {
104 let Some(url) = client_config.meta::<String>(field_name)? else {
105 return Ok(BTreeMap::new());
106 };
107 let response = reqwest
108 .get(&url)
109 .send()
110 .await
111 .context("Meta override source could not be fetched")?;
112
113 debug!("Meta override source returned status: {response:?}");
114
115 if response.status() != reqwest::StatusCode::OK {
116 bail!(
117 "Meta override request returned non-OK status code: {}",
118 response.status()
119 );
120 }
121
122 let mut federation_map = response
123 .json::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
124 .await
125 .context("Meta override could not be parsed as JSON")?;
126
127 let federation_id = client_config.calculate_federation_id().to_string();
128 let meta_fields = federation_map
129 .remove(&federation_id)
130 .with_context(|| anyhow::format_err!("No entry for federation {federation_id} in {url}"))?
131 .into_iter()
132 .filter_map(|(key, value)| {
133 if let serde_json::Value::String(value_str) = value {
134 Some((MetaFieldKey(key), MetaFieldValue(value_str)))
135 } else {
136 warn!(target: LOG_CLIENT, "Meta override map contained non-string key: {key}, ignoring");
137 None
138 }
139 })
140 .collect::<BTreeMap<_, _>>();
141
142 Ok(meta_fields)
143}