referrerpolicy=no-referrer-when-downgrade

cumulus_client_consensus_aura/collators/
mod.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//! Stock, pure Aura collators.
19//!
20//! This includes the [`basic`] collator, which only builds on top of the most recently
21//! included parachain block, as well as the [`lookahead`] collator, which prospectively
22//! builds on parachain blocks which have not yet been included in the relay chain.
23
24use crate::collator::SlotClaim;
25use codec::Codec;
26use cumulus_client_consensus_common::{self as consensus_common, ParentSearchParams};
27use cumulus_primitives_aura::{AuraUnincludedSegmentApi, Slot};
28use cumulus_primitives_core::{relay_chain::Header as RelayHeader, BlockT};
29use cumulus_relay_chain_interface::RelayChainInterface;
30use polkadot_node_subsystem::messages::RuntimeApiRequest;
31use polkadot_node_subsystem_util::runtime::ClaimQueueSnapshot;
32use polkadot_primitives::{
33	Hash as RelayHash, Id as ParaId, OccupiedCoreAssumption, ValidationCodeHash,
34	DEFAULT_SCHEDULING_LOOKAHEAD,
35};
36use sc_consensus_aura::{standalone as aura_internal, AuraApi};
37use sp_api::{ApiExt, ProvideRuntimeApi, RuntimeApiInfo};
38use sp_core::Pair;
39use sp_keystore::KeystorePtr;
40use sp_timestamp::Timestamp;
41
42pub mod basic;
43pub mod lookahead;
44pub mod slot_based;
45
46// This is an arbitrary value which is likely guaranteed to exceed any reasonable
47// limit, as it would correspond to 30 non-included blocks.
48//
49// Since we only search for parent blocks which have already been imported,
50// we can guarantee that all imported blocks respect the unincluded segment
51// rules specified by the parachain's runtime and thus will never be too deep. This is just an extra
52// sanity check.
53const PARENT_SEARCH_DEPTH: usize = 30;
54
55/// Check the `local_validation_code_hash` against the validation code hash in the relay chain
56/// state.
57///
58/// If the code hashes do not match, it prints a warning.
59async fn check_validation_code_or_log(
60	local_validation_code_hash: &ValidationCodeHash,
61	para_id: ParaId,
62	relay_client: &impl RelayChainInterface,
63	relay_parent: RelayHash,
64) {
65	let state_validation_code_hash = match relay_client
66		.validation_code_hash(relay_parent, para_id, OccupiedCoreAssumption::Included)
67		.await
68	{
69		Ok(hash) => hash,
70		Err(error) => {
71			tracing::debug!(
72				target: super::LOG_TARGET,
73				%error,
74				?relay_parent,
75				%para_id,
76				"Failed to fetch validation code hash",
77			);
78			return
79		},
80	};
81
82	match state_validation_code_hash {
83		Some(state) =>
84			if state != *local_validation_code_hash {
85				tracing::warn!(
86					target: super::LOG_TARGET,
87					%para_id,
88					?relay_parent,
89					?local_validation_code_hash,
90					relay_validation_code_hash = ?state,
91					"Parachain code doesn't match validation code stored in the relay chain state.",
92				);
93			},
94		None => {
95			tracing::warn!(
96				target: super::LOG_TARGET,
97				%para_id,
98				?relay_parent,
99				"Could not find validation code for parachain in the relay chain state.",
100			);
101		},
102	}
103}
104
105/// Fetch scheduling lookahead at given relay parent.
106async fn scheduling_lookahead(
107	relay_parent: RelayHash,
108	relay_client: &impl RelayChainInterface,
109) -> Option<u32> {
110	let runtime_api_version = relay_client
111		.version(relay_parent)
112		.await
113		.map_err(|e| {
114			tracing::error!(
115				target: super::LOG_TARGET,
116				error = ?e,
117				"Failed to fetch relay chain runtime version.",
118			)
119		})
120		.ok()?;
121
122	let parachain_host_runtime_api_version = runtime_api_version
123		.api_version(
124			&<dyn polkadot_primitives::runtime_api::ParachainHost<polkadot_primitives::Block>>::ID,
125		)
126		.unwrap_or_default();
127
128	if parachain_host_runtime_api_version <
129		RuntimeApiRequest::SCHEDULING_LOOKAHEAD_RUNTIME_REQUIREMENT
130	{
131		return None
132	}
133
134	match relay_client.scheduling_lookahead(relay_parent).await {
135		Ok(scheduling_lookahead) => Some(scheduling_lookahead),
136		Err(err) => {
137			tracing::error!(
138				target: crate::LOG_TARGET,
139				?err,
140				?relay_parent,
141				"Failed to fetch scheduling lookahead from relay chain",
142			);
143			None
144		},
145	}
146}
147
148// Returns the claim queue at the given relay parent.
149async fn claim_queue_at(
150	relay_parent: RelayHash,
151	relay_client: &impl RelayChainInterface,
152) -> ClaimQueueSnapshot {
153	// Get `ClaimQueue` from runtime
154	match relay_client.claim_queue(relay_parent).await {
155		Ok(claim_queue) => claim_queue.into(),
156		Err(error) => {
157			tracing::error!(
158				target: crate::LOG_TARGET,
159				?error,
160				?relay_parent,
161				"Failed to query claim queue runtime API",
162			);
163			Default::default()
164		},
165	}
166}
167
168// Checks if we own the slot at the given block and whether there
169// is space in the unincluded segment.
170async fn can_build_upon<Block: BlockT, Client, P>(
171	para_slot: Slot,
172	relay_slot: Slot,
173	timestamp: Timestamp,
174	parent_hash: Block::Hash,
175	included_block: Block::Hash,
176	client: &Client,
177	keystore: &KeystorePtr,
178) -> Option<SlotClaim<P::Public>>
179where
180	Client: ProvideRuntimeApi<Block>,
181	Client::Api: AuraApi<Block, P::Public> + AuraUnincludedSegmentApi<Block> + ApiExt<Block>,
182	P: Pair,
183	P::Public: Codec,
184	P::Signature: Codec,
185{
186	let runtime_api = client.runtime_api();
187	let authorities = runtime_api.authorities(parent_hash).ok()?;
188	let author_pub = aura_internal::claim_slot::<P>(para_slot, &authorities, keystore).await?;
189
190	// This function is typically called when we want to build block N. At that point, the
191	// unincluded segment in the runtime is unaware of the hash of block N-1. If the unincluded
192	// segment in the runtime is full, but block N-1 is the included block, the unincluded segment
193	// should have length 0 and we can build. Since the hash is not available to the runtime
194	// however, we need this extra check here.
195	if parent_hash == included_block {
196		return Some(SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp));
197	}
198
199	let api_version = runtime_api
200		.api_version::<dyn AuraUnincludedSegmentApi<Block>>(parent_hash)
201		.ok()
202		.flatten()?;
203
204	let slot = if api_version > 1 { relay_slot } else { para_slot };
205
206	runtime_api
207		.can_build_upon(parent_hash, included_block, slot)
208		.ok()?
209		.then(|| SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp))
210}
211
212/// Use [`cumulus_client_consensus_common::find_potential_parents`] to find parachain blocks that
213/// we can build on. Once a list of potential parents is retrieved, return the last one of the
214/// longest chain.
215async fn find_parent<Block>(
216	relay_parent: RelayHash,
217	para_id: ParaId,
218	para_backend: &impl sc_client_api::Backend<Block>,
219	relay_client: &impl RelayChainInterface,
220) -> Option<(<Block as BlockT>::Header, consensus_common::PotentialParent<Block>)>
221where
222	Block: BlockT,
223{
224	let parent_search_params = ParentSearchParams {
225		relay_parent,
226		para_id,
227		ancestry_lookback: scheduling_lookahead(relay_parent, relay_client)
228			.await
229			.unwrap_or(DEFAULT_SCHEDULING_LOOKAHEAD)
230			.saturating_sub(1) as usize,
231		max_depth: PARENT_SEARCH_DEPTH,
232		ignore_alternative_branches: true,
233	};
234
235	let potential_parents = cumulus_client_consensus_common::find_potential_parents::<Block>(
236		parent_search_params,
237		para_backend,
238		relay_client,
239	)
240	.await;
241
242	let potential_parents = match potential_parents {
243		Err(e) => {
244			tracing::error!(
245				target: crate::LOG_TARGET,
246				?relay_parent,
247				err = ?e,
248				"Could not fetch potential parents to build upon"
249			);
250
251			return None
252		},
253		Ok(x) => x,
254	};
255
256	let included_block = potential_parents.iter().find(|x| x.depth == 0)?.header.clone();
257	potential_parents
258		.into_iter()
259		.max_by_key(|a| a.depth)
260		.map(|parent| (included_block, parent))
261}
262
263#[cfg(test)]
264mod tests {
265	use crate::collators::can_build_upon;
266	use codec::Encode;
267	use cumulus_primitives_aura::Slot;
268	use cumulus_primitives_core::BlockT;
269	use cumulus_relay_chain_interface::PHash;
270	use cumulus_test_client::{
271		runtime::{Block, Hash},
272		Client, DefaultTestClientBuilderExt, InitBlockBuilder, TestClientBuilder,
273		TestClientBuilderExt,
274	};
275	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
276	use polkadot_primitives::HeadData;
277	use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy};
278	use sp_consensus::BlockOrigin;
279	use sp_keystore::{Keystore, KeystorePtr};
280	use sp_timestamp::Timestamp;
281	use std::sync::Arc;
282
283	async fn import_block<I: BlockImport<Block>>(
284		importer: &I,
285		block: Block,
286		origin: BlockOrigin,
287		import_as_best: bool,
288	) {
289		let (header, body) = block.deconstruct();
290
291		let mut block_import_params = BlockImportParams::new(origin, header);
292		block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(import_as_best));
293		block_import_params.body = Some(body);
294		importer.import_block(block_import_params).await.unwrap();
295	}
296
297	fn sproof_with_parent_by_hash(client: &Client, hash: PHash) -> RelayStateSproofBuilder {
298		let header = client.header(hash).ok().flatten().expect("No header for parent block");
299		let included = HeadData(header.encode());
300		let mut builder = RelayStateSproofBuilder::default();
301		builder.para_id = cumulus_test_client::runtime::PARACHAIN_ID.into();
302		builder.included_para_head = Some(included);
303
304		builder
305	}
306	async fn build_and_import_block(client: &Client, included: Hash) -> Block {
307		let sproof = sproof_with_parent_by_hash(client, included);
308
309		let block_builder = client.init_block_builder(None, sproof).block_builder;
310
311		let block = block_builder.build().unwrap().block;
312
313		let origin = BlockOrigin::NetworkInitialSync;
314		import_block(client, block.clone(), origin, true).await;
315		block
316	}
317
318	fn set_up_components() -> (Arc<Client>, KeystorePtr) {
319		let keystore = Arc::new(sp_keystore::testing::MemoryKeystore::new()) as Arc<_>;
320		for key in sp_keyring::Sr25519Keyring::iter() {
321			Keystore::sr25519_generate_new(
322				&*keystore,
323				sp_application_crypto::key_types::AURA,
324				Some(&key.to_seed()),
325			)
326			.expect("Can insert key into MemoryKeyStore");
327		}
328		(Arc::new(TestClientBuilder::new().build()), keystore)
329	}
330
331	/// This tests a special scenario where the unincluded segment in the runtime
332	/// is full. We are calling `can_build_upon`, passing the last built block as the
333	/// included one. In the runtime we will not find the hash of the included block in the
334	/// unincluded segment. The `can_build_upon` runtime API would therefore return `false`, but
335	/// we are ensuring on the node side that we are are always able to build on the included block.
336	#[tokio::test]
337	async fn test_can_build_upon() {
338		let (client, keystore) = set_up_components();
339
340		let genesis_hash = client.chain_info().genesis_hash;
341		let mut last_hash = genesis_hash;
342
343		// Fill up the unincluded segment tracker in the runtime.
344		while can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>(
345			Slot::from(u64::MAX),
346			Slot::from(u64::MAX),
347			Timestamp::default(),
348			last_hash,
349			genesis_hash,
350			&*client,
351			&keystore,
352		)
353		.await
354		.is_some()
355		{
356			let block = build_and_import_block(&client, genesis_hash).await;
357			last_hash = block.header().hash();
358		}
359
360		// Blocks were built with the genesis hash set as included block.
361		// We call `can_build_upon` with the last built block as the included block.
362		let result = can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>(
363			Slot::from(u64::MAX),
364			Slot::from(u64::MAX),
365			Timestamp::default(),
366			last_hash,
367			last_hash,
368			&*client,
369			&keystore,
370		)
371		.await;
372		assert!(result.is_some());
373	}
374}
375
376/// Holds a relay parent and its descendants.
377pub struct RelayParentData {
378	/// The relay parent block header
379	relay_parent: RelayHeader,
380	/// Ordered collection of descendant block headers, from oldest to newest
381	descendants: Vec<RelayHeader>,
382}
383
384impl RelayParentData {
385	/// Creates a new instance with the given relay parent and no descendants.
386	pub fn new(relay_parent: RelayHeader) -> Self {
387		Self { relay_parent, descendants: Default::default() }
388	}
389
390	/// Creates a new instance with the given relay parent and descendants.
391	pub fn new_with_descendants(relay_parent: RelayHeader, descendants: Vec<RelayHeader>) -> Self {
392		Self { relay_parent, descendants }
393	}
394
395	/// Returns a reference to the relay parent header.
396	pub fn relay_parent(&self) -> &RelayHeader {
397		&self.relay_parent
398	}
399
400	/// Returns the number of descendants.
401	#[cfg(test)]
402	pub fn descendants_len(&self) -> usize {
403		self.descendants.len()
404	}
405
406	/// Consumes the structure and returns a vector containing the relay parent followed by its
407	/// descendants in chronological order. The resulting list should be provided to the parachain
408	/// inherent data.
409	pub fn into_inherent_descendant_list(self) -> Vec<RelayHeader> {
410		let Self { relay_parent, mut descendants } = self;
411
412		if descendants.is_empty() {
413			return Default::default()
414		}
415
416		let mut result = vec![relay_parent];
417		result.append(&mut descendants);
418		result
419	}
420}