referrerpolicy=no-referrer-when-downgrade

cumulus_client_consensus_aura/collators/
lookahead.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//! A collator for Aura that looks ahead of the most recently included parachain block
19//! when determining what to build upon.
20//!
21//! This collator also builds additional blocks when the maximum backlog is not saturated.
22//! The size of the backlog is determined by invoking a runtime API. If that runtime API
23//! is not supported, this assumes a maximum backlog size of 1.
24//!
25//! This takes more advantage of asynchronous backing, though not complete advantage.
26//! When the backlog is not saturated, this approach lets the backlog temporarily 'catch up'
27//! with periods of higher throughput. When the backlog is saturated, we typically
28//! fall back to the limited cadence of a single parachain block per relay-chain block.
29//!
30//! Despite this, the fact that there is a backlog at all allows us to spend more time
31//! building the block, as there is some buffer before it can get posted to the relay-chain.
32//! The main limitation is block propagation time - i.e. the new blocks created by an author
33//! must be propagated to the next author before their turn.
34
35use codec::{Codec, Encode};
36use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface;
37use cumulus_client_consensus_common::{
38	self as consensus_common, ParachainBlockImportMarker, ParentSearchParams,
39};
40use cumulus_primitives_aura::AuraUnincludedSegmentApi;
41use cumulus_primitives_core::{
42	CollectCollationInfo, KeyToIncludeInRelayProof, PersistedValidationData,
43};
44use cumulus_relay_chain_interface::RelayChainInterface;
45use polkadot_node_primitives::{SegmentCollation, SubmitSegmentParams};
46use polkadot_node_subsystem::messages::CollationGenerationMessage;
47use polkadot_overseer::Handle as OverseerHandle;
48use polkadot_primitives::{
49	CandidateDescriptorVersion, CollatorPair, Id as ParaId, OccupiedCoreAssumption,
50};
51use sp_consensus::Environment;
52
53use crate::{
54	collator as collator_util,
55	collators::{claim_queue_at, BackingGroupConnectionHelper},
56	export_pov_to_path,
57};
58use futures::prelude::*;
59use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf};
60use sc_consensus::BlockImport;
61use sc_network_types::PeerId;
62use sp_api::{ApiExt, ProvideRuntimeApi};
63use sp_application_crypto::AppPublic;
64use sp_blockchain::HeaderBackend;
65use sp_consensus_aura::{AuraApi, Slot};
66use sp_core::crypto::Pair;
67use sp_inherents::CreateInherentDataProviders;
68use sp_keystore::KeystorePtr;
69use sp_runtime::{
70	traits::{Block as BlockT, Header as HeaderT, Member},
71	BoundedVec, Saturating,
72};
73use sp_timestamp::Timestamp;
74use std::{path::PathBuf, sync::Arc, time::Duration};
75
76/// Parameters for [`run`].
77pub struct Params<BI, CIDP, Client, Backend, RClient, CHP, ProposerFactory, CS> {
78	/// Inherent data providers. Only non-consensus inherent data should be provided, i.e.
79	/// the timestamp, slot, and paras inherents should be omitted, as they are set by this
80	/// collator.
81	pub create_inherent_data_providers: CIDP,
82	/// Used to actually import blocks.
83	pub block_import: BI,
84	/// The underlying para client.
85	pub para_client: Arc<Client>,
86	/// The para client's backend, used to access the database.
87	pub para_backend: Arc<Backend>,
88	/// A handle to the relay-chain client.
89	pub relay_client: RClient,
90	/// A validation code hash provider, used to get the current validation code hash.
91	pub code_hash_provider: CHP,
92	/// The underlying keystore, which should contain Aura consensus keys.
93	pub keystore: KeystorePtr,
94	/// The collator key used to sign collations before submitting to validators.
95	pub collator_key: CollatorPair,
96	/// The collator network peer id.
97	pub collator_peer_id: PeerId,
98	/// The para's ID.
99	pub para_id: ParaId,
100	/// A handle to the relay-chain client's "Overseer" or task orchestrator.
101	pub overseer_handle: OverseerHandle,
102	/// The length of slots in the relay chain.
103	pub relay_chain_slot_duration: Duration,
104	/// The proposer for building blocks.
105	pub proposer: ProposerFactory,
106	/// The generic collator service used to plug into this consensus engine.
107	pub collator_service: CS,
108	/// The amount of time to spend authoring each block.
109	pub authoring_duration: Duration,
110	/// Whether we should reinitialize the collator config (i.e. we are transitioning to aura).
111	pub reinitialize: bool,
112	/// The maximum percentage of the maximum PoV size that the collator can use.
113	/// It will be removed once <https://github.com/paritytech/polkadot-sdk/issues/6020> is fixed.
114	pub max_pov_percentage: Option<u32>,
115}
116
117/// Get the current parachain slot from a given block hash.
118///
119/// Returns the parachain slot, relay chain slot, and timestamp.
120fn get_parachain_slot<Block, Client, P>(
121	para_client: &Client,
122	block_hash: Block::Hash,
123	relay_parent_header: &polkadot_primitives::Header,
124	relay_chain_slot_duration: Duration,
125) -> Option<(Slot, Slot, Timestamp)>
126where
127	Block: BlockT,
128	Client: ProvideRuntimeApi<Block>,
129	Client::Api: AuraApi<Block, P>,
130	P: Codec,
131{
132	let slot_duration =
133		match sc_consensus_aura::standalone::slot_duration_at(para_client, block_hash) {
134			Ok(sd) => sd,
135			Err(err) => {
136				tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to acquire parachain slot duration");
137				return None;
138			},
139		};
140
141	tracing::debug!(target: crate::LOG_TARGET, ?slot_duration, ?block_hash, "Parachain slot duration acquired");
142
143	let (relay_slot, timestamp) = consensus_common::get_relay_slot_and_timestamp(
144		relay_parent_header,
145		relay_chain_slot_duration,
146	)?;
147
148	let slot_now = Slot::from_timestamp(timestamp, slot_duration);
149
150	tracing::debug!(
151		target: crate::LOG_TARGET,
152		?relay_slot,
153		para_slot = ?slot_now,
154		?timestamp,
155		?slot_duration,
156		?relay_chain_slot_duration,
157		"Adjusted relay-chain slot to parachain slot"
158	);
159
160	Some((slot_now, relay_slot, timestamp))
161}
162
163/// Run async-backing-friendly Aura.
164pub fn run<Block, P, BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS>(
165	params: Params<BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS>,
166) -> impl Future<Output = ()> + Send + 'static
167where
168	Block: BlockT,
169	Client: ProvideRuntimeApi<Block>
170		+ BlockOf
171		+ AuxStore
172		+ HeaderBackend<Block>
173		+ BlockBackend<Block>
174		+ Send
175		+ Sync
176		+ 'static,
177	Client::Api: AuraApi<Block, P::Public>
178		+ CollectCollationInfo<Block>
179		+ AuraUnincludedSegmentApi<Block>
180		+ KeyToIncludeInRelayProof<Block>,
181	Backend: sc_client_api::Backend<Block> + 'static,
182	RClient: RelayChainInterface + Clone + 'static,
183	CIDP: CreateInherentDataProviders<Block, ()> + 'static,
184	CIDP::InherentDataProviders: Send,
185	BI: BlockImport<Block> + ParachainBlockImportMarker + Send + Sync + 'static,
186	Proposer: Environment<Block> + Clone + Send + Sync + 'static,
187	CS: CollatorServiceInterface<Block> + Send + Sync + 'static,
188	CHP: consensus_common::ValidationCodeHashProvider<Block::Hash> + Send + 'static,
189	P: Pair + Send + Sync + 'static,
190	P::Public: AppPublic + Member + Codec,
191	P::Signature: TryFrom<Vec<u8>> + Member + Codec,
192{
193	run_with_export::<_, P, _, _, _, _, _, _, _, _>(ParamsWithExport { params, export_pov: None })
194}
195
196/// Parameters for [`run_with_export`].
197pub struct ParamsWithExport<BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS> {
198	/// The parameters.
199	pub params: Params<BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS>,
200
201	/// When set, the collator will export every produced `POV` to this folder.
202	pub export_pov: Option<PathBuf>,
203}
204
205/// Run async-backing-friendly Aura.
206///
207/// This is exactly the same as [`run`], but it supports the optional export of each produced `POV`
208/// to the file system.
209pub fn run_with_export<Block, P, BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS>(
210	ParamsWithExport { mut params, export_pov }: ParamsWithExport<
211		BI,
212		CIDP,
213		Client,
214		Backend,
215		RClient,
216		CHP,
217		Proposer,
218		CS,
219	>,
220) -> impl Future<Output = ()> + Send + 'static
221where
222	Block: BlockT,
223	Client: ProvideRuntimeApi<Block>
224		+ BlockOf
225		+ AuxStore
226		+ HeaderBackend<Block>
227		+ BlockBackend<Block>
228		+ Send
229		+ Sync
230		+ 'static,
231	Client::Api: AuraApi<Block, P::Public>
232		+ CollectCollationInfo<Block>
233		+ AuraUnincludedSegmentApi<Block>
234		+ KeyToIncludeInRelayProof<Block>,
235	Backend: sc_client_api::Backend<Block> + 'static,
236	RClient: RelayChainInterface + Clone + 'static,
237	CIDP: CreateInherentDataProviders<Block, ()> + 'static,
238	CIDP::InherentDataProviders: Send,
239	BI: BlockImport<Block> + ParachainBlockImportMarker + Send + Sync + 'static,
240	Proposer: Environment<Block> + Clone + Send + Sync + 'static,
241	CS: CollatorServiceInterface<Block> + Send + Sync + 'static,
242	CHP: consensus_common::ValidationCodeHashProvider<Block::Hash> + Send + 'static,
243	P: Pair + Send + Sync + 'static,
244	P::Public: AppPublic + Member + Codec,
245	P::Signature: TryFrom<Vec<u8>> + Member + Codec,
246{
247	async move {
248		cumulus_client_collator::initialize_collator_subsystems(
249			&mut params.overseer_handle,
250			params.collator_key,
251			params.para_id,
252			params.reinitialize,
253		)
254		.await;
255
256		let mut import_notifications = match params.relay_client.import_notification_stream().await
257		{
258			Ok(s) => s,
259			Err(err) => {
260				tracing::error!(
261					target: crate::LOG_TARGET,
262					?err,
263					"Failed to initialize consensus: no relay chain import notification stream"
264				);
265
266				return;
267			},
268		};
269
270		let mut collator = {
271			let params = collator_util::Params {
272				create_inherent_data_providers: params.create_inherent_data_providers,
273				block_import: params.block_import,
274				relay_client: params.relay_client.clone(),
275				keystore: params.keystore.clone(),
276				collator_peer_id: params.collator_peer_id,
277				para_id: params.para_id,
278				proposer: params.proposer,
279				collator_service: params.collator_service,
280			};
281
282			collator_util::Collator::<Block, P, _, _, _, _, _>::new(params)
283		};
284
285		let mut connection_helper = BackingGroupConnectionHelper::new(
286			params.keystore.clone(),
287			params.overseer_handle.clone(),
288		);
289
290		while let Some(relay_parent_header) = import_notifications.next().await {
291			let relay_parent = relay_parent_header.hash();
292
293			let Some(core_index) = claim_queue_at(relay_parent, &mut params.relay_client)
294				.await
295				.iter_claims_at_depth_for_para(0, params.para_id)
296				.next()
297			else {
298				tracing::trace!(
299					target: crate::LOG_TARGET,
300					?relay_parent,
301					?params.para_id,
302					"Para is not scheduled on any core, skipping import notification",
303				);
304
305				continue;
306			};
307
308			let max_pov_size = match params
309				.relay_client
310				.persisted_validation_data(
311					relay_parent,
312					params.para_id,
313					OccupiedCoreAssumption::Included,
314				)
315				.await
316			{
317				Ok(None) => continue,
318				Ok(Some(pvd)) => pvd.max_pov_size,
319				Err(err) => {
320					tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to gather information from relay-client");
321					continue;
322				},
323			};
324
325			let session_index =
326				match params.relay_client.session_index_for_child(relay_parent).await {
327					Ok(session_index) => session_index,
328					Err(err) => {
329						tracing::error!(
330							target: crate::LOG_TARGET,
331							?err,
332							?relay_parent,
333							"Failed to fetch session index."
334						);
335						continue;
336					},
337				};
338
339			let parent_search_result = match crate::collators::find_parent(
340				&params.relay_client,
341				&*params.para_backend,
342				params.para_id,
343				ParentSearchParams::V2 { scheduling_parent: relay_parent },
344				|_| true,
345			)
346			.await
347			{
348				Some(result) => result,
349				None => continue,
350			};
351
352			let included_header = &parent_search_result.included_at_scheduling;
353			let para_client = &*params.para_client;
354			let keystore = &params.keystore;
355			let included_block_hash = included_header.hash();
356
357			// Note that the authorities can change at any block, so we need to re-claim our slot
358			// on every relay parent.
359			let parent_hash = parent_search_result.best_parent_header.hash();
360			let parent_header = parent_search_result.best_parent_header;
361			// Distance from included block to best parent.
362			let initial_parent_depth =
363				(*parent_header.number()).saturating_sub(*included_header.number());
364			let overseer_handle = &mut params.overseer_handle;
365
366			// Do not try to build upon an unknown, pruned or bad block
367			if !collator.collator_service().check_block_status(parent_hash, &parent_header) {
368				continue;
369			}
370
371			// Trigger pre-connect to backing groups if necessary.
372			if let Some((slot_now, _relay_slot, _timestamp)) = get_parachain_slot::<_, _, P::Public>(
373				para_client,
374				parent_hash,
375				&relay_parent_header,
376				params.relay_chain_slot_duration,
377			) {
378				let mut runtime_api = para_client.runtime_api();
379				runtime_api
380					.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
381				if let Ok(authorities) = runtime_api.authorities(parent_hash) {
382					connection_helper.update::<P>(slot_now, &authorities).await;
383				}
384			}
385
386			let Some((slot_now, relay_slot, timestamp)) = get_parachain_slot::<_, _, P::Public>(
387				para_client,
388				parent_hash,
389				&relay_parent_header,
390				params.relay_chain_slot_duration,
391			) else {
392				continue;
393			};
394
395			let Some(slot_claim) = super::claim_slot::<_, _, P>(
396				slot_now,
397				timestamp,
398				parent_hash,
399				para_client,
400				&keystore,
401			)
402			.await
403			else {
404				continue;
405			};
406
407			if !super::can_build_upon::<_, _>(
408				parent_hash,
409				included_block_hash,
410				relay_slot,
411				slot_now,
412				para_client,
413			)
414			.await
415			{
416				continue;
417			}
418
419			tracing::debug!(
420				target: crate::LOG_TARGET,
421				?relay_parent,
422				unincluded_segment_len = ?initial_parent_depth,
423				"Slot claimed. Building"
424			);
425
426			let validation_data = PersistedValidationData {
427				parent_head: parent_header.encode().into(),
428				relay_parent_number: *relay_parent_header.number(),
429				relay_parent_storage_root: *relay_parent_header.state_root(),
430				max_pov_size,
431			};
432
433			let relay_proof_request =
434				super::get_relay_proof_request(&*params.para_client, parent_hash);
435
436			let (parachain_inherent_data, other_inherent_data) = match collator
437				.create_inherent_data(
438					relay_parent,
439					&validation_data,
440					parent_hash,
441					slot_claim.timestamp(),
442					relay_proof_request,
443					params.collator_peer_id,
444				)
445				.await
446			{
447				Err(err) => {
448					tracing::error!(target: crate::LOG_TARGET, ?err);
449					continue;
450				},
451				Ok(x) => x,
452			};
453
454			let Some(validation_code_hash) = params.code_hash_provider.code_hash_at(parent_hash)
455			else {
456				tracing::error!(target: crate::LOG_TARGET, ?parent_hash, "Could not fetch validation code hash");
457				continue;
458			};
459
460			super::check_validation_code_or_log(
461				&validation_code_hash,
462				params.para_id,
463				&params.relay_client,
464				relay_parent,
465			)
466			.await;
467
468			let allowed_pov_size = if let Some(max_pov_percentage) = params.max_pov_percentage {
469				validation_data.max_pov_size * max_pov_percentage / 100
470			} else {
471				// Set the block limit to 85% of the maximum PoV size.
472				//
473				// Once https://github.com/paritytech/polkadot-sdk/issues/6020 issue is
474				// fixed, the reservation should be removed.
475				validation_data.max_pov_size * 85 / 100
476			} as usize;
477
478			let collation_result = collator
479				.collate(
480					&parent_header,
481					&slot_claim,
482					None,
483					(parachain_inherent_data, other_inherent_data),
484					params.authoring_duration,
485					allowed_pov_size,
486					None,
487				)
488				.await;
489
490			match collation_result {
491				Ok(Some((collation, block_data))) => {
492					let Some(new_block_header) =
493						block_data.blocks().first().map(|b| b.header().clone())
494					else {
495						tracing::error!(target: crate::LOG_TARGET,  "Produced PoV doesn't contain any blocks");
496						continue;
497					};
498
499					let new_block_hash = new_block_header.hash();
500
501					// Here we are assuming that the import logic protects against equivocations
502					// and provides sybil-resistance, as it should.
503					collator.collator_service().announce_block(new_block_hash, None);
504
505					if let Some(ref export_pov) = export_pov {
506						export_pov_to_path::<Block>(
507							export_pov,
508							collation.proof_of_validity.clone().into_compressed(),
509							new_block_hash,
510							*new_block_header.number(),
511							parent_header.clone(),
512							*relay_parent_header.state_root(),
513							*relay_parent_header.number(),
514							validation_data.max_pov_size,
515						);
516					}
517
518					// Send a submit-collation message to the collation generation subsystem,
519					// which then distributes this to validators.
520					//
521					// Here we are assuming that the leaf is imported, as we've gotten an
522					// import notification.
523					overseer_handle
524						.send_msg(
525							CollationGenerationMessage::SubmitSegment(SubmitSegmentParams {
526								scheduling_parent: relay_parent,
527								core_index,
528								candidates_descriptor_version: CandidateDescriptorVersion::V2,
529								collations: BoundedVec::try_from(vec![SegmentCollation {
530									relay_parent,
531									collation,
532									validation_code_hash,
533									session_index,
534									validation_data,
535								}])
536								.expect("One element segment should fit;qed!"),
537							}),
538							"SubmitSegment",
539						)
540						.await;
541				},
542				Ok(None) => {
543					tracing::debug!(target: crate::LOG_TARGET, "No block proposal");
544					continue;
545				},
546				Err(err) => {
547					tracing::error!(target: crate::LOG_TARGET, ?err);
548					continue;
549				},
550			}
551		}
552	}
553}