use anyhow::{anyhow, Context};
use reqwest::Url;
use serde::{Deserialize, Serialize};
use slog::{o, Logger};
use std::collections::HashMap;
use std::sync::Arc;
use mithril_common::api_version::APIVersionProvider;
use crate::aggregator_client::{AggregatorClient, AggregatorHTTPClient};
use crate::cardano_stake_distribution_client::CardanoStakeDistributionClient;
use crate::cardano_transaction_client::CardanoTransactionClient;
use crate::certificate_client::{
CertificateClient, CertificateVerifier, MithrilCertificateVerifier,
};
use crate::feedback::{FeedbackReceiver, FeedbackSender};
use crate::mithril_stake_distribution_client::MithrilStakeDistributionClient;
use crate::snapshot_client::SnapshotClient;
#[cfg(feature = "fs")]
use crate::snapshot_downloader::{HttpSnapshotDownloader, SnapshotDownloader};
use crate::MithrilResult;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ClientOptions {
pub http_headers: Option<HashMap<String, String>>,
#[cfg(target_family = "wasm")]
#[cfg_attr(target_family = "wasm", serde(default))]
pub unstable: bool,
}
impl ClientOptions {
pub fn new(http_headers: Option<HashMap<String, String>>) -> Self {
Self {
http_headers,
#[cfg(target_family = "wasm")]
unstable: false,
}
}
#[cfg(target_family = "wasm")]
pub fn with_unstable_features(self, unstable: bool) -> Self {
Self { unstable, ..self }
}
}
#[derive(Clone)]
pub struct Client {
cardano_transaction_client: Arc<CardanoTransactionClient>,
cardano_stake_distribution_client: Arc<CardanoStakeDistributionClient>,
certificate_client: Arc<CertificateClient>,
mithril_stake_distribution_client: Arc<MithrilStakeDistributionClient>,
snapshot_client: Arc<SnapshotClient>,
}
impl Client {
pub fn cardano_transaction(&self) -> Arc<CardanoTransactionClient> {
self.cardano_transaction_client.clone()
}
pub fn certificate(&self) -> Arc<CertificateClient> {
self.certificate_client.clone()
}
pub fn mithril_stake_distribution(&self) -> Arc<MithrilStakeDistributionClient> {
self.mithril_stake_distribution_client.clone()
}
pub fn snapshot(&self) -> Arc<SnapshotClient> {
self.snapshot_client.clone()
}
pub fn cardano_stake_distribution(&self) -> Arc<CardanoStakeDistributionClient> {
self.cardano_stake_distribution_client.clone()
}
}
pub struct ClientBuilder {
aggregator_endpoint: Option<String>,
genesis_verification_key: String,
aggregator_client: Option<Arc<dyn AggregatorClient>>,
certificate_verifier: Option<Arc<dyn CertificateVerifier>>,
#[cfg(feature = "fs")]
snapshot_downloader: Option<Arc<dyn SnapshotDownloader>>,
logger: Option<Logger>,
feedback_receivers: Vec<Arc<dyn FeedbackReceiver>>,
options: ClientOptions,
}
impl ClientBuilder {
pub fn aggregator(endpoint: &str, genesis_verification_key: &str) -> ClientBuilder {
Self {
aggregator_endpoint: Some(endpoint.to_string()),
genesis_verification_key: genesis_verification_key.to_string(),
aggregator_client: None,
certificate_verifier: None,
#[cfg(feature = "fs")]
snapshot_downloader: None,
logger: None,
feedback_receivers: vec![],
options: ClientOptions::default(),
}
}
pub fn new(genesis_verification_key: &str) -> ClientBuilder {
Self {
aggregator_endpoint: None,
genesis_verification_key: genesis_verification_key.to_string(),
aggregator_client: None,
certificate_verifier: None,
#[cfg(feature = "fs")]
snapshot_downloader: None,
logger: None,
feedback_receivers: vec![],
options: ClientOptions::default(),
}
}
pub fn build(self) -> MithrilResult<Client> {
let logger = self
.logger
.unwrap_or_else(|| Logger::root(slog::Discard, o!()));
let feedback_sender = FeedbackSender::new(&self.feedback_receivers);
let aggregator_client = match self.aggregator_client {
None => {
let endpoint = self
.aggregator_endpoint
.ok_or(anyhow!("No aggregator endpoint set: \
You must either provide an aggregator endpoint or your own AggregatorClient implementation"))?;
let endpoint_url = Url::parse(&endpoint)
.with_context(|| format!("Invalid aggregator endpoint, it must be a correctly formed url: '{endpoint}'"))?;
Arc::new(
AggregatorHTTPClient::new(
endpoint_url,
APIVersionProvider::compute_all_versions_sorted()
.with_context(|| "Could not compute aggregator api versions")?,
logger.clone(),
self.options.http_headers,
)
.with_context(|| "Building aggregator client failed")?,
)
}
Some(client) => client,
};
#[cfg(feature = "fs")]
let snapshot_downloader = match self.snapshot_downloader {
None => Arc::new(
HttpSnapshotDownloader::new(feedback_sender.clone(), logger.clone())
.with_context(|| "Building snapshot downloader failed")?,
),
Some(snapshot_downloader) => snapshot_downloader,
};
let cardano_transaction_client =
Arc::new(CardanoTransactionClient::new(aggregator_client.clone()));
let certificate_verifier = match self.certificate_verifier {
None => Arc::new(
MithrilCertificateVerifier::new(
aggregator_client.clone(),
&self.genesis_verification_key,
feedback_sender.clone(),
logger.clone(),
)
.with_context(|| "Building certificate verifier failed")?,
),
Some(verifier) => verifier,
};
let certificate_client = Arc::new(CertificateClient::new(
aggregator_client.clone(),
certificate_verifier,
logger.clone(),
));
let mithril_stake_distribution_client = Arc::new(MithrilStakeDistributionClient::new(
aggregator_client.clone(),
));
let snapshot_client = Arc::new(SnapshotClient::new(
aggregator_client.clone(),
#[cfg(feature = "fs")]
snapshot_downloader,
#[cfg(feature = "fs")]
feedback_sender,
#[cfg(feature = "fs")]
logger,
));
let cardano_stake_distribution_client =
Arc::new(CardanoStakeDistributionClient::new(aggregator_client));
Ok(Client {
cardano_transaction_client,
cardano_stake_distribution_client,
certificate_client,
mithril_stake_distribution_client,
snapshot_client,
})
}
pub fn with_aggregator_client(
mut self,
aggregator_client: Arc<dyn AggregatorClient>,
) -> ClientBuilder {
self.aggregator_client = Some(aggregator_client);
self
}
pub fn with_certificate_verifier(
mut self,
certificate_verifier: Arc<dyn CertificateVerifier>,
) -> ClientBuilder {
self.certificate_verifier = Some(certificate_verifier);
self
}
cfg_fs! {
pub fn with_snapshot_downloader(
mut self,
snapshot_downloader: Arc<dyn SnapshotDownloader>,
) -> ClientBuilder {
self.snapshot_downloader = Some(snapshot_downloader);
self
}
}
pub fn with_logger(mut self, logger: Logger) -> Self {
self.logger = Some(logger);
self
}
pub fn add_feedback_receiver(mut self, receiver: Arc<dyn FeedbackReceiver>) -> Self {
self.feedback_receivers.push(receiver);
self
}
pub fn with_options(mut self, options: ClientOptions) -> Self {
self.options = options;
self
}
}