// Copyright 2021 Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Cumulus is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . //! The relay-chain provided consensus algoritm for parachains. //! //! This is the simplest consensus algorithm you can use when developing a parachain. It is a //! permission-less consensus algorithm that doesn't require any staking or similar to join as a //! collator. In this algorithm the consensus is provided by the relay-chain. This works in the //! following way. //! //! 1. Each node that sees itself as a collator is free to build a parachain candidate. //! //! 2. This parachain candidate is send to the parachain validators that are part of the relay chain. //! //! 3. The parachain validators validate at most X different parachain candidates, where X is the //! total number of parachain validators. //! //! 4. The parachain candidate that is backed by the most validators is choosen by the relay-chain //! block producer to be added as backed candidate on chain. //! //! 5. After the parachain candidate got backed and included, all collators start at 1. use cumulus_client_consensus_common::{ ParachainBlockImport, ParachainCandidate, ParachainConsensus, }; use cumulus_primitives_core::{ relay_chain::v1::{Block as PBlock, Hash as PHash, ParachainHost}, ParaId, PersistedValidationData, }; use parking_lot::Mutex; use polkadot_client::ClientHandle; use sc_client_api::Backend; use sc_consensus::{BlockImport, BlockImportParams}; use sp_api::ProvideRuntimeApi; use sp_consensus::{ BlockOrigin, EnableProofRecording, Environment, ProofRecording, Proposal, Proposer, }; use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT}; use std::{marker::PhantomData, sync::Arc, time::Duration}; mod import_queue; pub use import_queue::{import_queue, Verifier}; const LOG_TARGET: &str = "cumulus-consensus-relay-chain"; /// The implementation of the relay-chain provided consensus for parachains. pub struct RelayChainConsensus { para_id: ParaId, _phantom: PhantomData, proposer_factory: Arc>, create_inherent_data_providers: Arc, block_import: Arc>>, relay_chain_client: Arc, relay_chain_backend: Arc, } impl Clone for RelayChainConsensus { fn clone(&self) -> Self { Self { para_id: self.para_id, _phantom: PhantomData, proposer_factory: self.proposer_factory.clone(), create_inherent_data_providers: self.create_inherent_data_providers.clone(), block_import: self.block_import.clone(), relay_chain_backend: self.relay_chain_backend.clone(), relay_chain_client: self.relay_chain_client.clone(), } } } impl RelayChainConsensus where B: BlockT, RClient: ProvideRuntimeApi, RClient::Api: ParachainHost, RBackend: Backend, CIDP: CreateInherentDataProviders, { /// Create a new instance of relay-chain provided consensus. pub fn new( para_id: ParaId, proposer_factory: PF, create_inherent_data_providers: CIDP, block_import: BI, polkadot_client: Arc, polkadot_backend: Arc, ) -> Self { Self { para_id, proposer_factory: Arc::new(Mutex::new(proposer_factory)), create_inherent_data_providers: Arc::new(create_inherent_data_providers), block_import: Arc::new(futures::lock::Mutex::new(ParachainBlockImport::new( block_import, ))), relay_chain_backend: polkadot_backend, relay_chain_client: polkadot_client, _phantom: PhantomData, } } /// Get the inherent data with validation function parameters injected async fn inherent_data( &self, parent: B::Hash, validation_data: &PersistedValidationData, relay_parent: PHash, ) -> Option { let inherent_data_providers = self .create_inherent_data_providers .create_inherent_data_providers(parent, (relay_parent, validation_data.clone())) .await .map_err(|e| { tracing::error!( target: LOG_TARGET, error = ?e, "Failed to create inherent data providers.", ) }) .ok()?; inherent_data_providers .create_inherent_data() .map_err(|e| { tracing::error!( target: LOG_TARGET, error = ?e, "Failed to create inherent data.", ) }) .ok() } } #[async_trait::async_trait] impl ParachainConsensus for RelayChainConsensus where B: BlockT, RClient: ProvideRuntimeApi + Send + Sync, RClient::Api: ParachainHost, RBackend: Backend, BI: BlockImport + Send + Sync, PF: Environment + Send + Sync, PF::Proposer: Proposer< B, Transaction = BI::Transaction, ProofRecording = EnableProofRecording, Proof = ::Proof, >, CIDP: CreateInherentDataProviders, { async fn produce_candidate( &mut self, parent: &B::Header, relay_parent: PHash, validation_data: &PersistedValidationData, ) -> Option> { let proposer_future = self.proposer_factory.lock().init(&parent); let proposer = proposer_future .await .map_err( |e| tracing::error!(target: LOG_TARGET, error = ?e, "Could not create proposer."), ) .ok()?; let inherent_data = self.inherent_data(parent.hash(), &validation_data, relay_parent).await?; let Proposal { block, storage_changes, proof } = proposer .propose( inherent_data, Default::default(), //TODO: Fix this. Duration::from_millis(500), // Set the block limit to 50% of the maximum PoV size. // // TODO: If we got benchmarking that includes that encapsulates the proof size, // we should be able to use the maximum pov size. Some((validation_data.max_pov_size / 2) as usize), ) .await .map_err(|e| tracing::error!(target: LOG_TARGET, error = ?e, "Proposing failed.")) .ok()?; let (header, extrinsics) = block.clone().deconstruct(); let mut block_import_params = BlockImportParams::new(BlockOrigin::Own, header); block_import_params.body = Some(extrinsics); block_import_params.state_action = sc_consensus::StateAction::ApplyChanges( sc_consensus::StorageChanges::Changes(storage_changes), ); if let Err(err) = self .block_import .lock() .await .import_block(block_import_params, Default::default()) .await { tracing::error!( target: LOG_TARGET, at = ?parent.hash(), error = ?err, "Error importing build block.", ); return None } Some(ParachainCandidate { block, proof }) } } /// Paramaters of [`build_relay_chain_consensus`]. pub struct BuildRelayChainConsensusParams { pub para_id: ParaId, pub proposer_factory: PF, pub create_inherent_data_providers: CIDP, pub block_import: BI, pub relay_chain_client: polkadot_client::Client, pub relay_chain_backend: Arc, } /// Build the [`RelayChainConsensus`]. /// /// Returns a boxed [`ParachainConsensus`]. pub fn build_relay_chain_consensus( BuildRelayChainConsensusParams { para_id, proposer_factory, create_inherent_data_providers, block_import, relay_chain_client, relay_chain_backend, }: BuildRelayChainConsensusParams, ) -> Box> where Block: BlockT, PF: Environment + Send + Sync + 'static, PF::Proposer: Proposer< Block, Transaction = BI::Transaction, ProofRecording = EnableProofRecording, Proof = ::Proof, >, BI: BlockImport + Send + Sync + 'static, RBackend: Backend + 'static, CIDP: CreateInherentDataProviders + 'static, { RelayChainConsensusBuilder::new( para_id, proposer_factory, block_import, create_inherent_data_providers, relay_chain_client, relay_chain_backend, ) .build() } /// Relay chain consensus builder. /// /// Builds a [`RelayChainConsensus`] for a parachain. As this requires /// a concrete relay chain client instance, the builder takes a [`polkadot_client::Client`] /// that wraps this concrete instanace. By using [`polkadot_client::ExecuteWithClient`] /// the builder gets access to this concrete instance. struct RelayChainConsensusBuilder { para_id: ParaId, _phantom: PhantomData, proposer_factory: PF, create_inherent_data_providers: CIDP, block_import: BI, relay_chain_backend: Arc, relay_chain_client: polkadot_client::Client, } impl RelayChainConsensusBuilder where Block: BlockT, PF: Environment + Send + Sync + 'static, PF::Proposer: Proposer< Block, Transaction = BI::Transaction, ProofRecording = EnableProofRecording, Proof = ::Proof, >, BI: BlockImport + Send + Sync + 'static, RBackend: Backend + 'static, CIDP: CreateInherentDataProviders + 'static, { /// Create a new instance of the builder. fn new( para_id: ParaId, proposer_factory: PF, block_import: BI, create_inherent_data_providers: CIDP, relay_chain_client: polkadot_client::Client, relay_chain_backend: Arc, ) -> Self { Self { para_id, _phantom: PhantomData, proposer_factory, block_import, create_inherent_data_providers, relay_chain_backend, relay_chain_client, } } /// Build the relay chain consensus. fn build(self) -> Box> { self.relay_chain_client.clone().execute_with(self) } } impl polkadot_client::ExecuteWithClient for RelayChainConsensusBuilder where Block: BlockT, PF: Environment + Send + Sync + 'static, PF::Proposer: Proposer< Block, Transaction = BI::Transaction, ProofRecording = EnableProofRecording, Proof = ::Proof, >, BI: BlockImport + Send + Sync + 'static, RBackend: Backend + 'static, CIDP: CreateInherentDataProviders + 'static, { type Output = Box>; fn execute_with_client(self, client: Arc) -> Self::Output where >::StateBackend: sp_api::StateBackend>, PBackend: Backend, PBackend::State: sp_api::StateBackend, Api: polkadot_client::RuntimeApiCollection, PClient: polkadot_client::AbstractClient + 'static, { Box::new(RelayChainConsensus::new( self.para_id, self.proposer_factory, self.create_inherent_data_providers, self.block_import, client.clone(), self.relay_chain_backend, )) } }