use crate::error::CredentialProxyError;
use futures::{StreamExt, stream};
use nym_cache::CachedImmutableItems;
use nym_credentials::ecash::utils::{EcashTime, cred_exp_date, ecash_today};
use nym_validator_client::EcashApiClient;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
use std::cmp::min;
use std::future::Future;
use time::{Date, OffsetDateTime};
use tokio::sync::Mutex;
use tracing::warn;
pub struct CachedEpoch {
valid_until: OffsetDateTime,
pub current_epoch: Epoch,
}
impl Default for CachedEpoch {
fn default() -> Self {
CachedEpoch {
valid_until: OffsetDateTime::UNIX_EPOCH,
current_epoch: Epoch::default(),
}
}
}
impl CachedEpoch {
pub fn is_valid(&self) -> bool {
self.valid_until > OffsetDateTime::now_utc()
}
pub fn update(&mut self, epoch: Epoch) {
let now = OffsetDateTime::now_utc();
let validity_duration = if let Some(epoch_finish) = epoch.deadline {
#[allow(clippy::unwrap_used)]
let state_end =
OffsetDateTime::from_unix_timestamp(epoch_finish.seconds() as i64).unwrap();
let until_epoch_state_end = state_end - now;
min(until_epoch_state_end, 5 * time::Duration::MINUTE)
} else {
5 * time::Duration::MINUTE
};
self.valid_until = now + validity_duration;
self.current_epoch = epoch;
}
}
pub type CachedImmutableEpochItem<T> = CachedImmutableItems<EpochId, T>;
pub fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), CredentialProxyError> {
let today = ecash_today();
if expiration_date < today.date() {
return Err(CredentialProxyError::ExpirationDateTooEarly);
}
if expiration_date > cred_exp_date().ecash_date() {
return Err(CredentialProxyError::ExpirationDateTooLate);
}
Ok(())
}
pub async fn query_all_threshold_apis<F, T, U>(
all_apis: Vec<EcashApiClient>,
threshold: u64,
f: F,
) -> Result<Vec<T>, CredentialProxyError>
where
F: Fn(EcashApiClient) -> U,
U: Future<Output = Result<T, CredentialProxyError>>,
{
let shares = Mutex::new(Vec::with_capacity(all_apis.len()));
stream::iter(all_apis)
.for_each_concurrent(8, |api| async {
let disp = api.to_string();
match f(api).await {
Ok(partial_share) => shares.lock().await.push(partial_share),
Err(err) => {
warn!("failed to obtain partial threshold data from API: {disp}: {err}")
}
}
})
.await;
let shares = shares.into_inner();
if shares.len() < threshold as usize {
return Err(CredentialProxyError::InsufficientNumberOfSigners {
threshold,
available: shares.len(),
});
}
Ok(shares)
}