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, DecodeError, Encodable};
8use fedimint_core::module::registry::ModuleDecoderRegistry;
9use fedimint_core::task::{MaybeSend, MaybeSync};
10use fedimint_core::util::{FmtCompact as _, backoff_util, retry};
11use fedimint_core::{apply, async_trait_maybe_send};
12use fedimint_logging::LOG_CLIENT;
13use serde::{Deserialize, Serialize, de};
14use tracing::debug;
15
16#[apply(async_trait_maybe_send!)]
17pub trait MetaSource: MaybeSend + MaybeSync + 'static {
18 async fn wait_for_update(&self);
20 async fn fetch(
21 &self,
22 client_config: &ClientConfig,
23 api: &DynGlobalApi,
24 fetch_kind: FetchKind,
25 last_revision: Option<u64>,
26 ) -> anyhow::Result<MetaValues>;
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum FetchKind {
31 Initial,
34 Background,
36}
37
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39pub struct MetaValues {
40 pub values: BTreeMap<MetaFieldKey, MetaFieldValue>,
41 pub revision: u64,
42}
43
44#[derive(Debug, Clone, Copy)]
45pub struct MetaValue<T> {
46 pub fetch_time: SystemTime,
47 pub value: Option<T>,
48}
49
50#[derive(Clone, Debug, Default)]
53#[non_exhaustive]
54pub struct LegacyMetaSource {
55 reqwest: reqwest::Client,
56}
57
58#[apply(async_trait_maybe_send!)]
59impl MetaSource for LegacyMetaSource {
60 async fn wait_for_update(&self) {
61 fedimint_core::runtime::sleep(Duration::from_secs(10 * 60)).await;
62 }
63
64 async fn fetch(
65 &self,
66 client_config: &ClientConfig,
67 _api: &DynGlobalApi,
68 fetch_kind: FetchKind,
69 last_revision: Option<u64>,
70 ) -> anyhow::Result<MetaValues> {
71 let config_iter = client_config.global.meta.iter().map(|(key, value)| {
72 (
73 MetaFieldKey(key.clone()),
74 MetaFieldValue(serde_json::Value::String(value.clone())),
75 )
76 });
77 let backoff = match fetch_kind {
78 FetchKind::Initial => backoff_util::aggressive_backoff(),
80 FetchKind::Background => backoff_util::background_backoff(),
81 };
82 let overrides = retry("fetch_meta_overrides", backoff, || {
83 fetch_meta_overrides(&self.reqwest, client_config, "meta_override_url")
84 })
85 .await?;
86 Ok(MetaValues {
87 values: config_iter.chain(overrides).collect(),
88 revision: last_revision.map_or(0, |r| r + 1),
89 })
90 }
91}
92
93#[derive(
94 Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
95)]
96pub struct MetaFieldKey(pub String);
97
98#[derive(Debug, Clone, Serialize)]
99pub struct MetaFieldValue(pub serde_json::Value);
100
101impl<'de> Deserialize<'de> for MetaFieldValue {
109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110 where
111 D: de::Deserializer<'de>,
112 {
113 let value = serde_json::Value::deserialize(deserializer)?;
114
115 let final_value = if let serde_json::Value::String(s) = &value {
116 match serde_json::from_str::<serde_json::Value>(s) {
118 Ok(parsed) => parsed,
119 Err(_) => value, }
121 } else {
122 value
123 };
124
125 Ok(MetaFieldValue(final_value))
126 }
127}
128
129impl Encodable for MetaFieldValue {
130 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
131 let s = serde_json::to_string(&self).expect("Can't fail");
132
133 s.consensus_encode(writer)
134 }
135}
136
137impl Decodable for MetaFieldValue {
138 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
139 r: &mut R,
140 modules: &ModuleDecoderRegistry,
141 ) -> Result<Self, DecodeError> {
142 let s = String::consensus_decode_partial(r, modules)?;
143
144 Ok(Self(serde_json::from_str(&s).unwrap_or_else(|err| {
145 debug!(
146 target: LOG_CLIENT,
147 err = %err.fmt_compact(),
148 s = %s,
149 "Failed to decode meta value in the db as json, falling back to string"
150 );
151 serde_json::Value::String(s)
152 })))
153 }
154}
155
156pub async fn fetch_meta_overrides(
157 reqwest: &reqwest::Client,
158 client_config: &ClientConfig,
159 field_name: &str,
160) -> anyhow::Result<BTreeMap<MetaFieldKey, MetaFieldValue>> {
161 let Some(url) = client_config.meta::<String>(field_name)? else {
162 return Ok(BTreeMap::new());
163 };
164 let response = reqwest
165 .get(&url)
166 .send()
167 .await
168 .context("Meta override source could not be fetched")?;
169
170 debug!("Meta override source returned status: {response:?}");
171
172 if response.status() != reqwest::StatusCode::OK {
173 bail!(
174 "Meta override request returned non-OK status code: {}",
175 response.status()
176 );
177 }
178
179 let mut federation_map = response
180 .json::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
181 .await
182 .context("Meta override could not be parsed as JSON")?;
183
184 let federation_id = client_config.calculate_federation_id().to_string();
185 let meta_fields = federation_map
186 .remove(&federation_id)
187 .with_context(|| anyhow::format_err!("No entry for federation {federation_id} in {url}"))?
188 .into_iter()
189 .map(|(key, value)| (MetaFieldKey(key), MetaFieldValue(value)))
190 .collect::<BTreeMap<_, _>>();
191
192 Ok(meta_fields)
193}