use std::collections::BTreeMap;
use std::time::{Duration, SystemTime};
use anyhow::{Context as _, bail};
use fedimint_api_client::api::DynGlobalApi;
use fedimint_core::config::ClientConfig;
use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
use fedimint_core::module::registry::ModuleDecoderRegistry;
use fedimint_core::task::{MaybeSend, MaybeSync};
use fedimint_core::util::{FmtCompact as _, backoff_util, retry};
use fedimint_core::{apply, async_trait_maybe_send};
use fedimint_logging::LOG_CLIENT;
use serde::{Deserialize, Serialize, de};
use tracing::debug;
#[apply(async_trait_maybe_send!)]
pub trait MetaSource: MaybeSend + MaybeSync + 'static {
async fn wait_for_update(&self);
async fn fetch(
&self,
client_config: &ClientConfig,
api: &DynGlobalApi,
fetch_kind: FetchKind,
last_revision: Option<u64>,
) -> anyhow::Result<MetaValues>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FetchKind {
Initial,
Background,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MetaValues {
pub values: BTreeMap<MetaFieldKey, MetaFieldValue>,
pub revision: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct MetaValue<T> {
pub fetch_time: SystemTime,
pub value: Option<T>,
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct LegacyMetaSource {
reqwest: reqwest::Client,
}
#[apply(async_trait_maybe_send!)]
impl MetaSource for LegacyMetaSource {
async fn wait_for_update(&self) {
fedimint_core::runtime::sleep(Duration::from_mins(10)).await;
}
async fn fetch(
&self,
client_config: &ClientConfig,
_api: &DynGlobalApi,
fetch_kind: FetchKind,
last_revision: Option<u64>,
) -> anyhow::Result<MetaValues> {
let config_iter = client_config.global.meta.iter().map(|(key, value)| {
(
MetaFieldKey(key.clone()),
MetaFieldValue(serde_json::Value::String(value.clone())),
)
});
let backoff = match fetch_kind {
FetchKind::Initial => backoff_util::aggressive_backoff(),
FetchKind::Background => backoff_util::background_backoff(),
};
let overrides = retry("fetch_meta_overrides", backoff, || {
fetch_meta_overrides(&self.reqwest, client_config, "meta_override_url")
})
.await?;
Ok(MetaValues {
values: config_iter.chain(overrides).collect(),
revision: last_revision.map_or(0, |r| r + 1),
})
}
}
#[derive(
Encodable, Decodable, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
)]
pub struct MetaFieldKey(pub String);
#[derive(Debug, Clone, Serialize)]
pub struct MetaFieldValue(pub serde_json::Value);
impl<'de> Deserialize<'de> for MetaFieldValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let final_value = if let serde_json::Value::String(s) = &value {
match serde_json::from_str::<serde_json::Value>(s) {
Ok(parsed) => parsed,
Err(_) => value, }
} else {
value
};
Ok(MetaFieldValue(final_value))
}
}
impl Encodable for MetaFieldValue {
fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
let s = serde_json::to_string(&self).expect("Can't fail");
s.consensus_encode(writer)
}
}
impl Decodable for MetaFieldValue {
fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
r: &mut R,
modules: &ModuleDecoderRegistry,
) -> Result<Self, DecodeError> {
let s = String::consensus_decode_partial(r, modules)?;
Ok(Self(serde_json::from_str(&s).unwrap_or_else(|err| {
debug!(
target: LOG_CLIENT,
err = %err.fmt_compact(),
s = %s,
"Failed to decode meta value in the db as json, falling back to string"
);
serde_json::Value::String(s)
})))
}
}
pub async fn fetch_meta_overrides(
reqwest: &reqwest::Client,
client_config: &ClientConfig,
field_name: &str,
) -> anyhow::Result<BTreeMap<MetaFieldKey, MetaFieldValue>> {
let Some(url) = client_config.meta::<String>(field_name)? else {
return Ok(BTreeMap::new());
};
let response = reqwest
.get(&url)
.send()
.await
.context("Meta override source could not be fetched")?;
debug!("Meta override source returned status: {response:?}");
if response.status() != reqwest::StatusCode::OK {
bail!(
"Meta override request returned non-OK status code: {}",
response.status()
);
}
let mut federation_map = response
.json::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
.await
.context("Meta override could not be parsed as JSON")?;
let federation_id = client_config.calculate_federation_id().to_string();
let meta_fields = federation_map
.remove(&federation_id)
.with_context(|| anyhow::format_err!("No entry for federation {federation_id} in {url}"))?
.into_iter()
.map(|(key, value)| (MetaFieldKey(key), MetaFieldValue(value)))
.collect::<BTreeMap<_, _>>();
Ok(meta_fields)
}