mithril-client 0.14.5

Mithril client library
Documentation
//! Mechanisms to exchange data with an aggregator.
//!

use anyhow::Context;
use async_trait::async_trait;

use mithril_aggregator_client::AggregatorHttpClient;

#[cfg(feature = "unstable")]
use mithril_aggregator_client::query::{
    GetCardanoBlockProofQuery, GetCardanoBlocksTransactionsListQuery,
    GetCardanoBlocksTransactionsQuery, GetCardanoTransactionProofV2Query,
};

use mithril_aggregator_client::query::{
    GetAggregatorStatusQuery, GetCardanoDatabaseListQuery, GetCardanoDatabaseQuery,
    GetCardanoStakeDistributionQuery, GetCardanoStakeDistributionsListQuery,
    GetCardanoTransactionProofQuery, GetCardanoTransactionQuery, GetCardanoTransactionsListQuery,
    GetCertificateQuery, GetCertificatesListQuery, GetMithrilStakeDistributionQuery,
    GetMithrilStakeDistributionsListQuery,
    PostIncrementCardanoDatabaseAncillaryRestoredStatisticQuery,
    PostIncrementCardanoDatabaseImmutablesRestoredStatisticQuery,
    PostIncrementCardanoDatabaseRestorationStatisticQuery,
};

#[cfg(feature = "unstable")]
use crate::{
    CardanoBlocksProofs, CardanoBlocksTransactionsSnapshot,
    CardanoBlocksTransactionsSnapshotListItem, CardanoTransactionsProofsV2,
    cardano_block_client::CardanoBlockAggregatorRequest,
    cardano_transaction_v2_client::CardanoTransactionV2AggregatorRequest,
};

use crate::{
    CardanoDatabaseSnapshot, CardanoDatabaseSnapshotListItem, CardanoStakeDistribution,
    CardanoStakeDistributionListItem, CardanoTransactionSnapshot,
    CardanoTransactionSnapshotListItem, CardanoTransactionsProofs, MithrilCertificate,
    MithrilCertificateListItem, MithrilResult, MithrilStakeDistribution,
    MithrilStakeDistributionListItem, common::EpochSpecifier, era::FetchedEra,
};

use crate::{
    cardano_database_client::CardanoDatabaseAggregatorRequest,
    cardano_stake_distribution_client::CardanoStakeDistributionAggregatorRequest,
    cardano_transaction_client::CardanoTransactionAggregatorRequest,
    certificate_client::CertificateAggregatorRequest, era::EraFetcher,
    mithril_stake_distribution_client::MithrilStakeDistributionAggregatorRequest,
};

#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl CardanoDatabaseAggregatorRequest for AggregatorHttpClient {
    async fn list_latest(&self) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
        self.send(GetCardanoDatabaseListQuery::latest())
            .await
            .with_context(|| "Failed to list latest Cardano database v2 snapshots")
    }

    async fn list_by_epoch(
        &self,
        specifier: EpochSpecifier,
    ) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
        self.send(GetCardanoDatabaseListQuery::for_epoch(specifier))
            .await
            .with_context(|| {
                format!("Failed to list Cardano database v2 snapshots for epoch '{specifier}'")
            })
    }

    async fn get_by_hash(&self, hash: &str) -> MithrilResult<Option<CardanoDatabaseSnapshot>> {
        self.send(GetCardanoDatabaseQuery::by_hash(hash))
            .await
            .with_context(|| {
                format!("Failed to get Cardano database v2 snapshots with hash '{hash}'")
            })
    }

    async fn increment_cardano_database_complete_restoration_statistic(&self) -> MithrilResult<()> {
        self.send(PostIncrementCardanoDatabaseRestorationStatisticQuery::complete())
            .await
            .with_context(|| "Failed to increment Cardano database complete restoration statistic")
    }

    async fn increment_cardano_database_partial_restoration_statistic(&self) -> MithrilResult<()> {
        self.send(PostIncrementCardanoDatabaseRestorationStatisticQuery::partial())
            .await
            .with_context(|| "Failed to increment Cardano database partial restoration statistic")
    }

    async fn increment_immutables_snapshot_restored_statistic(
        &self,
        number_of_immutable_files_restored: u32,
    ) -> MithrilResult<()> {
        self.send(PostIncrementCardanoDatabaseImmutablesRestoredStatisticQuery::new(number_of_immutable_files_restored))
            .await
            .with_context(|| format!("Failed to increment Cardano database immutable files restored statistic, number of immutable files restored: '{number_of_immutable_files_restored}'"))
    }

    async fn increment_ancillary_downloaded_statistic(&self) -> MithrilResult<()> {
        self.send(PostIncrementCardanoDatabaseAncillaryRestoredStatisticQuery)
            .await
            .with_context(
                || "Failed to increment Cardano database ancillary files restored statistic",
            )
    }
}

#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl CardanoStakeDistributionAggregatorRequest for AggregatorHttpClient {
    async fn list_latest(&self) -> MithrilResult<Vec<CardanoStakeDistributionListItem>> {
        self.send(GetCardanoStakeDistributionsListQuery::latest())
            .await
            .with_context(|| "Failed to list latest Cardano stake distributions")
    }

    async fn get_by_hash(&self, hash: &str) -> MithrilResult<Option<CardanoStakeDistribution>> {
        self.send(GetCardanoStakeDistributionQuery::by_hash(hash))
            .await
            .with_context(|| format!("Failed to get Cardano stake distribution with hash '{hash}'"))
    }

    async fn get_by_epoch(
        &self,
        specifier: EpochSpecifier,
    ) -> MithrilResult<Option<CardanoStakeDistribution>> {
        self.send(GetCardanoStakeDistributionQuery::for_epoch(specifier))
            .await
            .with_context(|| {
                format!("Failed to get Cardano stake distribution for epoch '{specifier}'")
            })
    }
}

#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl CardanoTransactionAggregatorRequest for AggregatorHttpClient {
    async fn get_proof(
        &self,
        hashes: &[String],
    ) -> MithrilResult<Option<CardanoTransactionsProofs>> {
        self.send(GetCardanoTransactionProofQuery::for_hashes(hashes))
            .await
            .with_context(|| {
                format!("Failed to get Cardano transactions proofs for hashes '{hashes:?}'")
            })
    }

    async fn list_latest_snapshots(
        &self,
    ) -> MithrilResult<Vec<CardanoTransactionSnapshotListItem>> {
        self.send(GetCardanoTransactionsListQuery::latest())
            .await
            .with_context(|| "Failed to list latest Cardano transactions snapshots")
    }

    async fn get_snapshot(&self, hash: &str) -> MithrilResult<Option<CardanoTransactionSnapshot>> {
        self.send(GetCardanoTransactionQuery::by_hash(hash))
            .await
            .with_context(|| {
                format!("Failed to get Cardano transaction snapshot with hash '{hash}'")
            })
    }
}

cfg_unstable! {
    #[cfg_attr(target_family = "wasm", async_trait(?Send))]
    #[cfg_attr(not(target_family = "wasm"), async_trait)]
    impl CardanoTransactionV2AggregatorRequest for AggregatorHttpClient {
        async fn get_proof(
            &self,
            hashes: &[String],
        ) -> MithrilResult<Option<CardanoTransactionsProofsV2>> {
            self.send(GetCardanoTransactionProofV2Query::for_hashes(hashes))
                .await
                .with_context(|| {
                    format!("Failed to get Cardano transactions proofs for hashes '{hashes:?}'")
                })
        }

        async fn list_latest_snapshots(
            &self,
        ) -> MithrilResult<Vec<CardanoBlocksTransactionsSnapshotListItem>> {
            self.send(GetCardanoBlocksTransactionsListQuery::latest())
                .await
                .with_context(|| "Failed to list latest Cardano transactions v2 snapshots")
        }

        async fn get_snapshot(
            &self,
            hash: &str,
        ) -> MithrilResult<Option<CardanoBlocksTransactionsSnapshot>> {
            self.send(GetCardanoBlocksTransactionsQuery::by_hash(hash))
                .await
                .with_context(|| {
                    format!("Failed to get Cardano transaction v2 snapshot with hash '{hash}'")
                })
        }
    }

    #[cfg_attr(target_family = "wasm", async_trait(?Send))]
    #[cfg_attr(not(target_family = "wasm"), async_trait)]
    impl CardanoBlockAggregatorRequest for AggregatorHttpClient {
        async fn get_proof(&self, hashes: &[String]) -> MithrilResult<Option<CardanoBlocksProofs>> {
            self.send(GetCardanoBlockProofQuery::for_hashes(hashes))
                .await
                .with_context(|| format!("Failed to get Cardano blocks proofs for hashes '{hashes:?}'"))
        }

        async fn list_latest_snapshots(
            &self,
        ) -> MithrilResult<Vec<CardanoBlocksTransactionsSnapshotListItem>> {
            self.send(GetCardanoBlocksTransactionsListQuery::latest())
                .await
                .with_context(|| "Failed to list latest Cardano blocks snapshots")
        }

        async fn get_snapshot(
            &self,
            hash: &str,
        ) -> MithrilResult<Option<CardanoBlocksTransactionsSnapshot>> {
            self.send(GetCardanoBlocksTransactionsQuery::by_hash(hash))
                .await
                .with_context(|| format!("Failed to get Cardano block snapshot with hash '{hash}'"))
        }
    }
}

#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl CertificateAggregatorRequest for AggregatorHttpClient {
    async fn list_latest(&self) -> MithrilResult<Vec<MithrilCertificateListItem>> {
        self.send(GetCertificatesListQuery::latest())
            .await
            .with_context(|| "Failed to list latest certificates")
    }

    async fn get_by_hash(&self, hash: &str) -> MithrilResult<Option<MithrilCertificate>> {
        self.send(GetCertificateQuery::by_hash(hash))
            .await
            .with_context(|| format!("Failed to get certificate with hash '{hash}'"))
    }
}

#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl EraFetcher for AggregatorHttpClient {
    async fn fetch_current_era(&self) -> MithrilResult<FetchedEra> {
        let aggregator_status = self
            .send(GetAggregatorStatusQuery::current())
            .await
            .with_context(|| "Failed to get aggregator status")?;

        Ok(FetchedEra {
            era: aggregator_status.mithril_era.to_string(),
        })
    }
}

#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl MithrilStakeDistributionAggregatorRequest for AggregatorHttpClient {
    async fn list_latest(&self) -> MithrilResult<Vec<MithrilStakeDistributionListItem>> {
        self.send(GetMithrilStakeDistributionsListQuery::latest())
            .await
            .with_context(|| "Failed to list latest Mithril stake distributions")
    }

    async fn get_by_hash(&self, hash: &str) -> MithrilResult<Option<MithrilStakeDistribution>> {
        self.send(GetMithrilStakeDistributionQuery::by_hash(hash))
            .await
            .with_context(|| format!("Failed to get Mithril stake distribution with hash '{hash}'"))
    }
}

#[cfg(test)]
mod tests {
    use httpmock::MockServer;
    use serde_json::json;

    use mithril_common::messages::AggregatorStatusMessage;

    use crate::common::{SupportedEra, test::Dummy};
    use crate::test_utils::TestLogger;

    use super::*;

    fn setup_server_and_client() -> (MockServer, AggregatorHttpClient) {
        let server = MockServer::start();
        let client = AggregatorHttpClient::builder(server.base_url())
            .with_logger(TestLogger::stdout())
            .build()
            .unwrap();

        (server, client)
    }

    #[tokio::test]
    async fn extract_mithril_era_from_status_response() {
        let (server, client) = setup_server_and_client();
        server.mock(|when, then| {
            when.any_request();
            then.status(200).json_body(json!(AggregatorStatusMessage {
                mithril_era: SupportedEra::Lagrange,
                ..Dummy::dummy()
            }));
        });

        let mithril_era = client.fetch_current_era().await.unwrap();
        assert_eq!(mithril_era.era, SupportedEra::Lagrange.to_string());
    }
}