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