Skip to main content

fedimint_client_module/
meta.rs

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    /// Wait for next change in this source.
20    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    /// Meta source should return fast, retry less.
33    /// This blocks getting any meta values.
34    Initial,
35    /// Meta source can retry infinitely.
36    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/// Legacy non-meta module config source uses client config meta and
52/// meta_override_url meta field.
53#[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            // need to be fast the first time.
80            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
102// In the past we did not support native serde_values as values,
103// which required users to make the the complex meta values a json-escaped
104// strings which is ... bleh.
105//
106// This custom Deserialize impl. "unpeals" the extra layer of json-escaping,
107// if it passes, trying to support the old values like it. We probably
108// should remove this workaround in some future.
109impl<'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            // Try to parse the string as JSON
118            match serde_json::from_str::<serde_json::Value>(s) {
119                Ok(parsed) => parsed,
120                Err(_) => value, // If parsing fails, use the original string value
121            }
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}