mithril-common 0.6.67

Common types, interfaces, and utilities for Mithril nodes.
Documentation
use anyhow::Context;
use async_trait::async_trait;
use slog::{Logger, debug};
use std::sync::Arc;

use crate::{
    StdResult,
    entities::{
        BlockNumber, BlockNumberOffset, CardanoDbBeacon, Epoch, ProtocolMessage,
        ProtocolMessagePartKey, SignedEntityType,
    },
    logging::LoggerExtensions,
    signable_builder::{SignableBuilder, SignableSeedBuilder},
};

/// ArtifactBuilder Service trait
#[cfg_attr(test, mockall::automock)]
#[async_trait]
pub trait SignableBuilderService: Send + Sync {
    /// Compute signable from signed entity type
    async fn compute_protocol_message(
        &self,
        signed_entity_type: SignedEntityType,
    ) -> StdResult<ProtocolMessage>;
}

/// Mithril Signable Builder Service
pub struct MithrilSignableBuilderService {
    seed_signable_builder: Arc<dyn SignableSeedBuilder>,
    mithril_stake_distribution_builder: Arc<dyn SignableBuilder<Epoch>>,
    immutable_signable_builder: Arc<dyn SignableBuilder<CardanoDbBeacon>>,
    cardano_transactions_signable_builder: Arc<dyn SignableBuilder<BlockNumber>>,
    cardano_blocks_transactions_signable_builder:
        Arc<dyn SignableBuilder<(BlockNumber, BlockNumberOffset)>>,
    cardano_stake_distribution_builder: Arc<dyn SignableBuilder<Epoch>>,
    cardano_database_signable_builder: Arc<dyn SignableBuilder<CardanoDbBeacon>>,
    logger: Logger,
}

/// SignableBuilders dependencies required by the [MithrilSignableBuilderService].
pub struct SignableBuilderServiceDependencies {
    mithril_stake_distribution_builder: Arc<dyn SignableBuilder<Epoch>>,
    immutable_signable_builder: Arc<dyn SignableBuilder<CardanoDbBeacon>>,
    cardano_transactions_signable_builder: Arc<dyn SignableBuilder<BlockNumber>>,
    cardano_blocks_transactions_signable_builder:
        Arc<dyn SignableBuilder<(BlockNumber, BlockNumberOffset)>>,
    cardano_stake_distribution_builder: Arc<dyn SignableBuilder<Epoch>>,
    cardano_database_signable_builder: Arc<dyn SignableBuilder<CardanoDbBeacon>>,
}

impl SignableBuilderServiceDependencies {
    /// Create a new instance of [SignableBuilderServiceDependencies].
    pub fn new(
        mithril_stake_distribution_builder: Arc<dyn SignableBuilder<Epoch>>,
        immutable_signable_builder: Arc<dyn SignableBuilder<CardanoDbBeacon>>,
        cardano_transactions_signable_builder: Arc<dyn SignableBuilder<BlockNumber>>,
        cardano_blocks_transactions_signable_builder: Arc<
            dyn SignableBuilder<(BlockNumber, BlockNumberOffset)>,
        >,
        cardano_stake_distribution_builder: Arc<dyn SignableBuilder<Epoch>>,
        cardano_database_signable_builder: Arc<dyn SignableBuilder<CardanoDbBeacon>>,
    ) -> Self {
        Self {
            mithril_stake_distribution_builder,
            immutable_signable_builder,
            cardano_transactions_signable_builder,
            cardano_blocks_transactions_signable_builder,
            cardano_stake_distribution_builder,
            cardano_database_signable_builder,
        }
    }
}

impl MithrilSignableBuilderService {
    /// MithrilSignableBuilderService factory
    pub fn new(
        seed_signable_builder: Arc<dyn SignableSeedBuilder>,
        dependencies: SignableBuilderServiceDependencies,
        logger: Logger,
    ) -> Self {
        Self {
            seed_signable_builder,
            mithril_stake_distribution_builder: dependencies.mithril_stake_distribution_builder,
            immutable_signable_builder: dependencies.immutable_signable_builder,
            cardano_transactions_signable_builder: dependencies
                .cardano_transactions_signable_builder,
            cardano_blocks_transactions_signable_builder: dependencies
                .cardano_blocks_transactions_signable_builder,
            cardano_stake_distribution_builder: dependencies.cardano_stake_distribution_builder,
            cardano_database_signable_builder: dependencies.cardano_database_signable_builder,
            logger: logger.new_with_component_name::<Self>(),
        }
    }

    async fn compute_signed_entity_protocol_message(
        &self,
        signed_entity_type: SignedEntityType,
    ) -> StdResult<ProtocolMessage> {
        debug!(
            self.logger,
            "Compute protocol message for signed entity type: '{signed_entity_type:?}'"
        );

        let protocol_message = match &signed_entity_type {
            SignedEntityType::MithrilStakeDistribution(e) => {
                self.mithril_stake_distribution_builder
                    .compute_protocol_message(*e)
                    .await
            }
            SignedEntityType::CardanoImmutableFilesFull(beacon) => {
                self.immutable_signable_builder
                    .compute_protocol_message(beacon.clone())
                    .await
            }
            SignedEntityType::CardanoStakeDistribution(e) => {
                self.cardano_stake_distribution_builder
                    .compute_protocol_message(*e)
                    .await
            }
            SignedEntityType::CardanoTransactions(_, block_number) => {
                self.cardano_transactions_signable_builder
                    .compute_protocol_message(*block_number)
                    .await
            }
            SignedEntityType::CardanoBlocksTransactions(_, block_number, block_number_offset) => {
                self.cardano_blocks_transactions_signable_builder
                    .compute_protocol_message((*block_number, *block_number_offset))
                    .await
            }
            SignedEntityType::CardanoDatabase(beacon) => {
                self.cardano_database_signable_builder
                    .compute_protocol_message(beacon.clone())
                    .await
            }
        }
        .with_context(|| {
            format!("Signable builder service can not compute protocol message for signed entity type: '{signed_entity_type:?}'")
        })?;

        Ok(protocol_message)
    }

    async fn compute_seeded_protocol_message(
        &self,
        protocol_message: ProtocolMessage,
    ) -> StdResult<ProtocolMessage> {
        let mut protocol_message = protocol_message;
        let next_aggregate_verification_key = self
            .seed_signable_builder
            .compute_next_aggregate_verification_key_for_concatenation()
            .await?;
        protocol_message.set_message_part(
            ProtocolMessagePartKey::NextAggregateVerificationKey,
            next_aggregate_verification_key,
        );

        #[cfg(feature = "future_snark")]
        if let Some(next_snark_aggregate_verification_key) = self
            .seed_signable_builder
            .compute_next_aggregate_verification_key_for_snark()
            .await?
        {
            protocol_message.set_message_part(
                ProtocolMessagePartKey::NextSnarkAggregateVerificationKey,
                next_snark_aggregate_verification_key,
            );
        }

        let next_protocol_parameters =
            self.seed_signable_builder.compute_next_protocol_parameters().await?;
        protocol_message.set_message_part(
            ProtocolMessagePartKey::NextProtocolParameters,
            next_protocol_parameters,
        );
        let current_epoch = self.seed_signable_builder.compute_current_epoch().await?;
        protocol_message.set_message_part(ProtocolMessagePartKey::CurrentEpoch, current_epoch);

        Ok(protocol_message)
    }
}

#[async_trait]
impl SignableBuilderService for MithrilSignableBuilderService {
    async fn compute_protocol_message(
        &self,
        signed_entity_type: SignedEntityType,
    ) -> StdResult<ProtocolMessage> {
        let protocol_message = self
            .compute_signed_entity_protocol_message(signed_entity_type)
            .await?;
        let protocol_message = self.compute_seeded_protocol_message(protocol_message).await?;

        Ok(protocol_message)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::{
        StdResult,
        entities::{BlockNumber, BlockNumberOffset, Epoch, ProtocolMessage},
        signable_builder::{Beacon as Beaconnable, MockSignableSeedBuilder, SignableBuilder},
        test::TestLogger,
    };

    use async_trait::async_trait;
    use mockall::mock;

    mock! {
        SignableBuilderImpl<U> { }

        #[async_trait]
        impl<U> SignableBuilder<U> for SignableBuilderImpl<U> where U: Beaconnable,
        {
            async fn compute_protocol_message(&self, beacon: U) -> StdResult<ProtocolMessage>;
        }
    }

    struct MockDependencyInjector {
        mock_signable_seed_builder: MockSignableSeedBuilder,
        mock_mithril_stake_distribution_signable_builder: MockSignableBuilderImpl<Epoch>,
        mock_cardano_immutable_files_full_signable_builder:
            MockSignableBuilderImpl<CardanoDbBeacon>,
        mock_cardano_transactions_signable_builder: MockSignableBuilderImpl<BlockNumber>,
        mock_cardano_blocks_transactions_signable_builder:
            MockSignableBuilderImpl<(BlockNumber, BlockNumberOffset)>,
        mock_cardano_stake_distribution_signable_builder: MockSignableBuilderImpl<Epoch>,
        mock_cardano_database_signable_builder: MockSignableBuilderImpl<CardanoDbBeacon>,
    }

    impl MockDependencyInjector {
        fn new() -> MockDependencyInjector {
            MockDependencyInjector {
                mock_signable_seed_builder: MockSignableSeedBuilder::new(),
                mock_mithril_stake_distribution_signable_builder: MockSignableBuilderImpl::new(),
                mock_cardano_immutable_files_full_signable_builder: MockSignableBuilderImpl::new(),
                mock_cardano_transactions_signable_builder: MockSignableBuilderImpl::new(),
                mock_cardano_blocks_transactions_signable_builder: MockSignableBuilderImpl::new(),
                mock_cardano_stake_distribution_signable_builder: MockSignableBuilderImpl::new(),
                mock_cardano_database_signable_builder: MockSignableBuilderImpl::new(),
            }
        }

        fn build_signable_builder_service(self) -> MithrilSignableBuilderService {
            let dependencies = SignableBuilderServiceDependencies::new(
                Arc::new(self.mock_mithril_stake_distribution_signable_builder),
                Arc::new(self.mock_cardano_immutable_files_full_signable_builder),
                Arc::new(self.mock_cardano_transactions_signable_builder),
                Arc::new(self.mock_cardano_blocks_transactions_signable_builder),
                Arc::new(self.mock_cardano_stake_distribution_signable_builder),
                Arc::new(self.mock_cardano_database_signable_builder),
            );

            MithrilSignableBuilderService::new(
                Arc::new(self.mock_signable_seed_builder),
                dependencies,
                TestLogger::stdout(),
            )
        }
    }

    fn build_mock_container() -> MockDependencyInjector {
        let mut mock_container = MockDependencyInjector::new();
        mock_container
            .mock_signable_seed_builder
            .expect_compute_next_aggregate_verification_key_for_concatenation()
            .once()
            .return_once(move || Ok("next-avk-123".to_string()));
        #[cfg(feature = "future_snark")]
        mock_container
            .mock_signable_seed_builder
            .expect_compute_next_aggregate_verification_key_for_snark()
            .once()
            .return_once(move || Ok(Some("next-snark-avk-123".to_string())));
        mock_container
            .mock_signable_seed_builder
            .expect_compute_next_protocol_parameters()
            .once()
            .return_once(move || Ok("protocol-params-hash-123".to_string()));
        mock_container
            .mock_signable_seed_builder
            .expect_compute_current_epoch()
            .once()
            .return_once(move || Ok("epoch-123".to_string()));

        mock_container
    }

    #[tokio::test]
    async fn build_mithril_stake_distribution_signable_when_given_mithril_stake_distribution_entity_type()
     {
        let mut mock_container = build_mock_container();
        mock_container
            .mock_mithril_stake_distribution_signable_builder
            .expect_compute_protocol_message()
            .once()
            .return_once(|_| Ok(ProtocolMessage::new()));
        let signable_builder_service = mock_container.build_signable_builder_service();
        let signed_entity_type = SignedEntityType::MithrilStakeDistribution(Epoch(1));

        signable_builder_service
            .compute_protocol_message(signed_entity_type)
            .await
            .unwrap();
    }

    #[tokio::test]
    async fn build_snapshot_signable_when_given_cardano_immutable_files_full_entity_type() {
        let mut mock_container = build_mock_container();
        mock_container
            .mock_cardano_immutable_files_full_signable_builder
            .expect_compute_protocol_message()
            .once()
            .return_once(|_| Ok(ProtocolMessage::new()));
        let signable_builder_service = mock_container.build_signable_builder_service();
        let signed_entity_type =
            SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::default());

        signable_builder_service
            .compute_protocol_message(signed_entity_type)
            .await
            .unwrap();
    }

    #[tokio::test]
    async fn build_transactions_signable_when_given_cardano_transactions_entity_type() {
        let mut mock_container = build_mock_container();
        mock_container
            .mock_cardano_transactions_signable_builder
            .expect_compute_protocol_message()
            .once()
            .return_once(|_| Ok(ProtocolMessage::new()));
        let signable_builder_service = mock_container.build_signable_builder_service();
        let signed_entity_type = SignedEntityType::CardanoTransactions(Epoch(5), BlockNumber(1000));

        signable_builder_service
            .compute_protocol_message(signed_entity_type)
            .await
            .unwrap();
    }

    #[tokio::test]
    async fn build_blocks_transactions_signable_when_given_cardano_blocks_transactions_entity_type()
    {
        let mut mock_container = build_mock_container();
        mock_container
            .mock_cardano_blocks_transactions_signable_builder
            .expect_compute_protocol_message()
            .once()
            .return_once(|_| Ok(ProtocolMessage::new()));
        let signable_builder_service = mock_container.build_signable_builder_service();
        let signed_entity_type = SignedEntityType::CardanoBlocksTransactions(
            Epoch(6),
            BlockNumber(1010),
            BlockNumberOffset(15),
        );

        signable_builder_service
            .compute_protocol_message(signed_entity_type)
            .await
            .unwrap();
    }

    #[tokio::test]
    async fn build_cardano_stake_distribution_signable_when_given_cardano_stake_distribution_entity_type()
     {
        let mut mock_container = build_mock_container();
        mock_container
            .mock_cardano_stake_distribution_signable_builder
            .expect_compute_protocol_message()
            .once()
            .return_once(|_| Ok(ProtocolMessage::new()));
        let signable_builder_service = mock_container.build_signable_builder_service();
        let signed_entity_type = SignedEntityType::CardanoStakeDistribution(Epoch(5));

        signable_builder_service
            .compute_protocol_message(signed_entity_type)
            .await
            .unwrap();
    }

    #[tokio::test]
    async fn build_cardano_database_signable_when_given_cardano_database_entity_type() {
        let mut mock_container = build_mock_container();
        mock_container
            .mock_cardano_database_signable_builder
            .expect_compute_protocol_message()
            .once()
            .return_once(|_| Ok(ProtocolMessage::new()));
        let signable_builder_service = mock_container.build_signable_builder_service();
        let signed_entity_type = SignedEntityType::CardanoDatabase(CardanoDbBeacon::default());

        signable_builder_service
            .compute_protocol_message(signed_entity_type)
            .await
            .unwrap();
    }
}