// Copyright 2020 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot 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. // Polkadot 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 Polkadot. If not, see . //! The block production pipeline of Polkadot. //! //! The `ProposerFactory` exported by this module will be wrapped by some //! consensus engine, and triggered when it is time to create a block. use std::{ pin::Pin, sync::Arc, time::{self, Duration, Instant}, }; use sp_blockchain::HeaderBackend; use block_builder::{BlockBuilderApi, BlockBuilderProvider}; use consensus::{Proposal, RecordProof}; use polkadot_primitives::{Block, Header}; use polkadot_primitives::parachain::{ ParachainHost, NEW_HEADS_IDENTIFIER, }; use runtime_primitives::traits::{DigestFor, HashFor}; use futures_timer::Delay; use txpool_api::TransactionPool; use futures::prelude::*; use inherents::InherentData; use sp_timestamp::TimestampInherentData; use sp_api::{ApiExt, ProvideRuntimeApi}; use prometheus_endpoint::Registry as PrometheusRegistry; use crate::{ Error, dynamic_inclusion::DynamicInclusion, validation_service::ServiceHandle, }; // Polkadot proposer factory. pub struct ProposerFactory { service_handle: ServiceHandle, babe_slot_duration: u64, factory: sc_basic_authorship::ProposerFactory, } impl ProposerFactory { /// Create a new proposer factory. pub fn new( client: Arc, transaction_pool: Arc, service_handle: ServiceHandle, babe_slot_duration: u64, prometheus: Option<&PrometheusRegistry>, ) -> Self { let factory = sc_basic_authorship::ProposerFactory::new( client, transaction_pool, prometheus, ); ProposerFactory { service_handle, babe_slot_duration, factory, } } } impl consensus::Environment for ProposerFactory where TxPool: TransactionPool + 'static, Client: BlockBuilderProvider + ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, Client::Api: ParachainHost + BlockBuilderApi + ApiExt, Backend: sc_client_api::Backend< Block, State = sp_api::StateBackendFor > + 'static, // Rust bug: https://github.com/rust-lang/rust/issues/24159 sp_api::StateBackendFor: sp_api::StateBackend> + Send, { type CreateProposer = Pin> + Send + 'static >>; type Proposer = Proposer; type Error = Error; fn init( &mut self, parent_header: &Header, ) -> Self::CreateProposer { let parent_hash = parent_header.hash(); let slot_duration = self.babe_slot_duration.clone(); let proposer = self.factory.init(parent_header).into_inner(); let maybe_proposer = self.service_handle .clone() .get_validation_instance(parent_hash) .and_then(move |tracker| future::ready(proposer .map_err(Into::into) .map(|proposer| Proposer { tracker, slot_duration, proposer, }) )); Box::pin(maybe_proposer) } } /// The Polkadot proposer logic. pub struct Proposer, Backend> { tracker: crate::validation_service::ValidationInstanceHandle, slot_duration: u64, proposer: sc_basic_authorship::Proposer, } impl consensus::Proposer for Proposer where TxPool: TransactionPool + 'static, Client: BlockBuilderProvider + ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, Client::Api: ParachainHost + BlockBuilderApi + ApiExt, Backend: sc_client_api::Backend> + 'static, // Rust bug: https://github.com/rust-lang/rust/issues/24159 sp_api::StateBackendFor: sp_api::StateBackend> + Send, { type Error = Error; type Transaction = sp_api::TransactionFor; type Proposal = Pin< Box< dyn Future>, Error>> + Send > >; fn propose( self, inherent_data: InherentData, inherent_digests: DigestFor, max_duration: Duration, record_proof: RecordProof, ) -> Self::Proposal { const SLOT_DURATION_DENOMINATOR: u64 = 3; // wait up to 1/3 of the slot for candidates. let initial_included = self.tracker.table().includable_count(); let now = Instant::now(); let dynamic_inclusion = DynamicInclusion::new( self.tracker.table().num_parachains(), self.tracker.started(), Duration::from_millis(self.slot_duration / SLOT_DURATION_DENOMINATOR), ); async move { let enough_candidates = dynamic_inclusion.acceptable_in( now, initial_included, ).unwrap_or_else(|| Duration::from_millis(1)); let believed_timestamp = match inherent_data.timestamp_inherent_data() { Ok(timestamp) => timestamp, Err(e) => return Err(Error::InherentError(e)), }; let deadline_diff = max_duration - max_duration / 3; // set up delay until next allowed timestamp. let current_timestamp = current_timestamp(); if current_timestamp < believed_timestamp { Delay::new(Duration::from_millis(current_timestamp - believed_timestamp)) .await; } Delay::new(enough_candidates).await; let proposed_candidates = self.tracker.table().proposed_set(); let mut inherent_data = inherent_data; inherent_data.put_data(NEW_HEADS_IDENTIFIER, &proposed_candidates) .map_err(Error::InherentError)?; self.proposer.propose( inherent_data, inherent_digests.clone(), deadline_diff, record_proof ).await.map_err(Into::into) }.boxed() } } fn current_timestamp() -> u64 { time::SystemTime::now().duration_since(time::UNIX_EPOCH) .expect("now always later than unix epoch; qed") .as_millis() as u64 }