Crate sidechain_mc_hash

Source
Expand description

Crate implementing a mechanism for Partner Chain blocks to reference a stable Cardano main chain block.

§Purpose of this crate

This crate provides a base for all other Partner Chain Toolkit’s features that require Cardano observability. Whenever some part of Cardano state is observed and included in a Partner Chain’s own state, all Partner Chain nodes need to agree on what data was eligible for inclusion when a PC block was produced. This is best achieved by first agreeing on a stable Cardano block which then serves as the upper boundary of all observed Cardano data. This main chain reference block is selected by the block producing node and must be accepted by other nodes as being stable at the time of block production.

This crate implements the [InherentDataProvider] and InherentDigest mechanism for selecting a reference main chain block as part of inherent data creation step and saving it as a main chain reference hash in the block header.

§Prerequisites

This feature uses the InherentDigest mechanism from sp_partner_chains_consensus_aura crate for storing inherent data in the block header. Your node must use the PartnerChainsProposer defined by that crate for this feature to work.

§Adding to the node

To add the feature to your Partner Chain node, follow the instructions below. Refer to the demo node implementation for reference.

§Data source

A data source implementing the McHashDataSource interface trait should be added to your node. A Db-Sync implementation can be found in the partner-chains-db-sync-data-sources crate. Refer to its documentation on how to configure and use it.

§Adding the inherent data provider

McHashInherentDataProvider should be added to your inherent data provider stack for both block proposal and verification, using dedicated constructor for each.

The main chain reference hash provided by McHashInherentDataProvider is meant to be used by other inherent data providers from the Partner Chains Toolkit which require Cardano observability. As such, it should be created first in your node’s IDP stack, so that McHashInherentDataProvider::mc_hash and McHashInherentDataProvider::previous_mc_hash can be used to pass the reference hashes to other IDPs.

As an example, creation for proposal would look like the following:

use sp_consensus_slots::{Slot, SlotDuration};
use sp_runtime::traits::Block as BlockT;

struct ProposalCIDP {
    // the data source should be created as part of your node's setup
    mc_hash_data_source: Arc<dyn McHashDataSource + Send + Sync>,
    // slot duration should either be a part of your node's configuration or be
    // retrieved using `parent_hash` and your consensus mechanism's runtime API
    slot_duration: SlotDuration,
}

#[async_trait::async_trait]
impl<Block> sp_inherents::CreateInherentDataProviders<Block, ()> for ProposalCIDP
where
    Block: BlockT + Send + Sync
{
    type InherentDataProviders = McHashInherentDataProvider;

    async fn create_inherent_data_providers(
        &self,
        parent_hash: <Block as BlockT>::Hash,
        _extra_args: (),
    ) -> Result<Self::InherentDataProviders, Box<dyn std::error::Error + Send + Sync>> {
        let parent_header: <Block as BlockT>::Header = unimplemented!("Retrieved from block store");
        let slot: Slot = unimplemented!("Provided by the consensus IDP");

        let mc_hash = McHashInherentDataProvider::new_proposal(
            parent_header,
            self.mc_hash_data_source.as_ref(),
            slot,
            self.slot_duration,
        ).await?;

        // Other providers can now use mc_hash.mc_hash() and mc_hash.previous_mc_hash()

        Ok((mc_hash /* other inherent data providers */))
    }
}

For block validation, creation of the IDP would look like this:

use sidechain_domain::McBlockHash;

struct VerificationIDP {
    // the data source should be created as part of your node's setup
    mc_hash_data_source: Arc<dyn McHashDataSource + Send + Sync>,
    // slot duration should either be a part of your node's configuration or be
    // retrieved using `parent_hash` and your consensus mechanism's runtime API
    slot_duration: SlotDuration,
}

#[async_trait::async_trait]
impl<Block> sp_inherents::CreateInherentDataProviders<Block, (Slot, McBlockHash)> for VerificationIDP
where
    Block: BlockT + Send + Sync
{
    type InherentDataProviders = McHashInherentDataProvider;

    async fn create_inherent_data_providers(
        &self,
        parent_hash: <Block as BlockT>::Hash,
        (block_slot_from_header, mc_hash_from_header): (Slot, McBlockHash),
    ) -> Result<Self::InherentDataProviders, Box<dyn std::error::Error + Send + Sync>> {
        let parent_header: <Block as BlockT>::Header = unimplemented!("Retrieved from block store");
        let slot: Slot = unimplemented!("Provided by the consensus IDP");
        let parent_slot: Option<Slot> = unimplemented!("Read from previous block using runtime API");

        let mc_hash = McHashInherentDataProvider::new_verification(
            parent_header,
            parent_slot,
            block_slot_from_header,
            mc_hash_from_header,
            self.slot_duration,
            self.mc_hash_data_source.as_ref(),
        )
        .await?;

        // Other providers can now use mc_hash.mc_hash() and mc_hash.previous_mc_hash()

        Ok((mc_hash /* other inherent data providers */))
    }
}

Note that for verification the implementation now accepts additional arguments of types [Slot] and McBlockHash which are provided by the import queue based on the header. In this case, the McBlockHash value comes from the InherentDigest defined in next section.

§Import queue configuration

To be able to save and retrieve inherent data in the block header, your node should use the PartnerChainsProposer instead of the stock proposer provided by Substrate. Refer to its documentation for more information.

For this feature to work, the only modification needed is to set McHashInherentDigest as the inherent digest type of the proposer. This will cause it to save the inherent data provided by McHashInherentDataProvider to be also saved in the block header and available for inspection and verification. Assuming your node uses PartnerChainsProposerFactory, this should look like the following:

use sidechain_mc_hash::McHashInherentDigest;
use sp_consensus::Environment;
use sp_partner_chains_consensus_aura::block_proposal::PartnerChainsProposerFactory;
use sp_runtime::traits::Block as BlockT;

fn new_full<Block: BlockT, ProposerFactory: Environment<Block>>(
	base_proposer_factory: ProposerFactory,
) {
    // .. other node setup logic
	let proposer_factory: PartnerChainsProposerFactory<
		Block,
		ProposerFactory,
		McHashInherentDigest,
	> = PartnerChainsProposerFactory::new(base_proposer_factory);
    // ..
}

Modules§

mock
Mock implementations of components in this crate for use in tests

Structs§

McHashInherentDataProvider
Inherent data provider that provides the hash of the latest stable Cardano block (main chain reference block) observed by the block producer to be included in the header of the currently produced PC block.
McHashInherentDigest
InherentDigest implementation for the main chain reference hash

Enums§

McHashInherentError
Error type returned by constructors of McHashInherentDataProvider

Constants§

INHERENT_IDENTIFIER
Inherent identifier under which the main chain block reference is provided
MC_HASH_DIGEST_ID
Digest ID of the main chain reference block hash

Traits§

McHashDataSource
Data source API used by McHashInherentDataProvider

Functions§

get_inherent_digest_value_for_block
get_mc_hash_for_block