referrerpolicy=no-referrer-when-downgrade

cumulus_client_consensus_aura/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// Cumulus is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// Cumulus is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with Cumulus. If not, see <https://www.gnu.org/licenses/>.
17
18//! The AuRa consensus algorithm for parachains.
19//!
20//! This extends the Substrate provided AuRa consensus implementation to make it compatible for
21//! parachains. The main entry points for of this consensus algorithm are [`AuraConsensus::build`]
22//! and [`fn@import_queue`].
23//!
24//! For more information about AuRa, the Substrate crate should be checked.
25
26use codec::{Codec, Encode};
27use cumulus_client_consensus_common::{
28	ParachainBlockImportMarker, ParachainCandidate, ParachainConsensus,
29};
30use cumulus_primitives_core::{relay_chain::Hash as PHash, PersistedValidationData};
31
32use cumulus_primitives_core::relay_chain::HeadData;
33use futures::lock::Mutex;
34use polkadot_primitives::{BlockNumber as RBlockNumber, Hash as RHash};
35use sc_client_api::{backend::AuxStore, BlockOf};
36use sc_consensus::BlockImport;
37use sc_consensus_slots::{BackoffAuthoringBlocksStrategy, SimpleSlotWorker, SlotInfo};
38use sc_telemetry::TelemetryHandle;
39use sp_api::ProvideRuntimeApi;
40use sp_application_crypto::AppPublic;
41use sp_blockchain::HeaderBackend;
42use sp_consensus::{EnableProofRecording, Environment, ProofRecording, Proposer, SyncOracle};
43use sp_consensus_aura::{AuraApi, SlotDuration};
44use sp_core::crypto::Pair;
45use sp_inherents::CreateInherentDataProviders;
46use sp_keystore::KeystorePtr;
47use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, NumberFor};
48use std::{
49	convert::TryFrom,
50	fs,
51	fs::File,
52	marker::PhantomData,
53	path::PathBuf,
54	sync::{
55		atomic::{AtomicU64, Ordering},
56		Arc,
57	},
58};
59
60mod import_queue;
61
62pub use import_queue::{build_verifier, import_queue, BuildVerifierParams, ImportQueueParams};
63use polkadot_node_primitives::PoV;
64pub use sc_consensus_aura::{
65	slot_duration, standalone::slot_duration_at, AuraVerifier, BuildAuraWorkerParams,
66	SlotProportion,
67};
68pub use sc_consensus_slots::InherentDataProviderExt;
69
70pub mod collator;
71pub mod collators;
72pub mod equivocation_import_queue;
73
74const LOG_TARGET: &str = "aura::cumulus";
75
76/// The implementation of the AURA consensus for parachains.
77pub struct AuraConsensus<B, CIDP, W> {
78	create_inherent_data_providers: Arc<CIDP>,
79	aura_worker: Arc<Mutex<W>>,
80	slot_duration: SlotDuration,
81	last_slot_processed: Arc<AtomicU64>,
82	_phantom: PhantomData<B>,
83}
84
85impl<B, CIDP, W> Clone for AuraConsensus<B, CIDP, W> {
86	fn clone(&self) -> Self {
87		Self {
88			create_inherent_data_providers: self.create_inherent_data_providers.clone(),
89			aura_worker: self.aura_worker.clone(),
90			slot_duration: self.slot_duration,
91			last_slot_processed: self.last_slot_processed.clone(),
92			_phantom: PhantomData,
93		}
94	}
95}
96
97/// Parameters of [`AuraConsensus::build`].
98#[deprecated = "Use the `aura::collators::basic` collator instead"]
99pub struct BuildAuraConsensusParams<PF, BI, CIDP, Client, BS, SO> {
100	pub proposer_factory: PF,
101	pub create_inherent_data_providers: CIDP,
102	pub block_import: BI,
103	pub para_client: Arc<Client>,
104	pub backoff_authoring_blocks: Option<BS>,
105	pub sync_oracle: SO,
106	pub keystore: KeystorePtr,
107	pub force_authoring: bool,
108	pub slot_duration: SlotDuration,
109	pub telemetry: Option<TelemetryHandle>,
110	pub block_proposal_slot_portion: SlotProportion,
111	pub max_block_proposal_slot_portion: Option<SlotProportion>,
112}
113
114impl<B, CIDP> AuraConsensus<B, CIDP, ()>
115where
116	B: BlockT,
117	CIDP: CreateInherentDataProviders<B, (PHash, PersistedValidationData)> + 'static,
118	CIDP::InherentDataProviders: InherentDataProviderExt,
119{
120	/// Create a new boxed instance of AURA consensus.
121	#[allow(deprecated)]
122	#[deprecated = "Use the `aura::collators::basic` collator instead"]
123	pub fn build<P, Client, BI, SO, PF, BS, Error>(
124		BuildAuraConsensusParams {
125			proposer_factory,
126			create_inherent_data_providers,
127			block_import,
128			para_client,
129			backoff_authoring_blocks,
130			sync_oracle,
131			keystore,
132			force_authoring,
133			slot_duration,
134			telemetry,
135			block_proposal_slot_portion,
136			max_block_proposal_slot_portion,
137		}: BuildAuraConsensusParams<PF, BI, CIDP, Client, BS, SO>,
138	) -> Box<dyn ParachainConsensus<B>>
139	where
140		Client:
141			ProvideRuntimeApi<B> + BlockOf + AuxStore + HeaderBackend<B> + Send + Sync + 'static,
142		Client::Api: AuraApi<B, P::Public>,
143		BI: BlockImport<B> + ParachainBlockImportMarker + Send + Sync + 'static,
144		SO: SyncOracle + Send + Sync + Clone + 'static,
145		BS: BackoffAuthoringBlocksStrategy<NumberFor<B>> + Send + Sync + 'static,
146		PF: Environment<B, Error = Error> + Send + Sync + 'static,
147		PF::Proposer: Proposer<
148			B,
149			Error = Error,
150			ProofRecording = EnableProofRecording,
151			Proof = <EnableProofRecording as ProofRecording>::Proof,
152		>,
153		Error: std::error::Error + Send + From<sp_consensus::Error> + 'static,
154		P: Pair + 'static,
155		P::Public: AppPublic + Member + Codec,
156		P::Signature: TryFrom<Vec<u8>> + Member + Codec,
157	{
158		let worker = sc_consensus_aura::build_aura_worker::<P, _, _, _, _, _, _, _, _>(
159			BuildAuraWorkerParams {
160				client: para_client,
161				block_import,
162				justification_sync_link: (),
163				proposer_factory,
164				sync_oracle,
165				force_authoring,
166				backoff_authoring_blocks,
167				keystore,
168				telemetry,
169				block_proposal_slot_portion,
170				max_block_proposal_slot_portion,
171				compatibility_mode: sc_consensus_aura::CompatibilityMode::None,
172			},
173		);
174
175		Box::new(AuraConsensus {
176			create_inherent_data_providers: Arc::new(create_inherent_data_providers),
177			aura_worker: Arc::new(Mutex::new(worker)),
178			last_slot_processed: Default::default(),
179			slot_duration,
180			_phantom: PhantomData,
181		})
182	}
183}
184
185impl<B, CIDP, W> AuraConsensus<B, CIDP, W>
186where
187	B: BlockT,
188	CIDP: CreateInherentDataProviders<B, (PHash, PersistedValidationData)> + 'static,
189	CIDP::InherentDataProviders: InherentDataProviderExt,
190{
191	/// Create the inherent data.
192	///
193	/// Returns the created inherent data and the inherent data providers used.
194	async fn inherent_data(
195		&self,
196		parent: B::Hash,
197		validation_data: &PersistedValidationData,
198		relay_parent: PHash,
199	) -> Option<CIDP::InherentDataProviders> {
200		self.create_inherent_data_providers
201			.create_inherent_data_providers(parent, (relay_parent, validation_data.clone()))
202			.await
203			.map_err(|e| {
204				tracing::error!(
205					target: LOG_TARGET,
206					error = ?e,
207					"Failed to create inherent data providers.",
208				)
209			})
210			.ok()
211	}
212}
213
214#[async_trait::async_trait]
215impl<B, CIDP, W> ParachainConsensus<B> for AuraConsensus<B, CIDP, W>
216where
217	B: BlockT,
218	CIDP: CreateInherentDataProviders<B, (PHash, PersistedValidationData)> + Send + Sync + 'static,
219	CIDP::InherentDataProviders: InherentDataProviderExt + Send,
220	W: SimpleSlotWorker<B> + Send + Sync,
221	W::Proposer: Proposer<B, Proof = <EnableProofRecording as ProofRecording>::Proof>,
222{
223	async fn produce_candidate(
224		&mut self,
225		parent: &B::Header,
226		relay_parent: PHash,
227		validation_data: &PersistedValidationData,
228	) -> Option<ParachainCandidate<B>> {
229		let inherent_data_providers =
230			self.inherent_data(parent.hash(), validation_data, relay_parent).await?;
231
232		let info = SlotInfo::new(
233			inherent_data_providers.slot(),
234			Box::new(inherent_data_providers),
235			self.slot_duration.as_duration(),
236			parent.clone(),
237			// Set the block limit to 50% of the maximum PoV size.
238			//
239			// TODO: If we got benchmarking that includes the proof size,
240			// we should be able to use the maximum pov size.
241			Some((validation_data.max_pov_size / 2) as usize),
242		);
243
244		// With async backing this function will be called every relay chain block.
245		//
246		// Most parachains currently run with 12 seconds slots and thus, they would try to produce
247		// multiple blocks per slot which very likely would fail on chain. Thus, we have this "hack"
248		// to only produce on block per slot.
249		//
250		// With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be
251		// obsolete and also the underlying issue will be fixed.
252		if self.last_slot_processed.fetch_max(*info.slot, Ordering::Relaxed) >= *info.slot {
253			return None
254		}
255
256		let res = self.aura_worker.lock().await.on_slot(info).await?;
257
258		Some(ParachainCandidate { block: res.block, proof: res.storage_proof })
259	}
260}
261
262/// Export the given `pov` to the file system at `path`.
263///
264/// The file will be named `block_hash_block_number.pov`.
265///
266/// The `parent_header`, `relay_parent_storage_root` and `relay_parent_number` will also be
267/// stored in the file alongside the `pov`. This enables stateless validation of the `pov`.
268pub(crate) fn export_pov_to_path<Block: BlockT>(
269	path: PathBuf,
270	pov: PoV,
271	block_hash: Block::Hash,
272	block_number: NumberFor<Block>,
273	parent_header: Block::Header,
274	relay_parent_storage_root: RHash,
275	relay_parent_number: RBlockNumber,
276	max_pov_size: u32,
277) {
278	if let Err(error) = fs::create_dir_all(&path) {
279		tracing::error!(target: LOG_TARGET, %error, path = %path.display(), "Failed to create PoV export directory");
280		return
281	}
282
283	let mut file = match File::create(path.join(format!("{block_hash:?}_{block_number}.pov"))) {
284		Ok(f) => f,
285		Err(error) => {
286			tracing::error!(target: LOG_TARGET, %error, "Failed to export PoV.");
287			return
288		},
289	};
290
291	pov.encode_to(&mut file);
292	PersistedValidationData {
293		parent_head: HeadData(parent_header.encode()),
294		relay_parent_number,
295		relay_parent_storage_root,
296		max_pov_size,
297	}
298	.encode_to(&mut file);
299}