referrerpolicy=no-referrer-when-downgrade

polkadot_node_subsystem_util/runtime/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Convenient interface to runtime information.
18
19use polkadot_node_primitives::MAX_FINALITY_LAG;
20use schnellru::{ByLength, LruMap};
21
22use codec::Encode;
23use sp_application_crypto::AppCrypto;
24use sp_core::crypto::ByteArray;
25use sp_keystore::{Keystore, KeystorePtr};
26
27use polkadot_node_subsystem::{
28	errors::RuntimeApiError,
29	messages::{RuntimeApiMessage, RuntimeApiRequest},
30	overseer, SubsystemSender,
31};
32use polkadot_node_subsystem_types::UnpinHandle;
33use polkadot_primitives::{
34	node_features::FeatureIndex, slashing, CandidateEvent, CandidateHash, CoreIndex, CoreState,
35	EncodeAs, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, IndexedVec, NodeFeatures,
36	OccupiedCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed, SigningContext,
37	UncheckedSigned, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
38	DEFAULT_SCHEDULING_LOOKAHEAD,
39};
40
41use std::collections::{BTreeMap, VecDeque};
42
43use crate::{
44	request_availability_cores, request_candidate_events, request_claim_queue,
45	request_disabled_validators, request_from_runtime, request_key_ownership_proof,
46	request_node_features, request_on_chain_votes, request_session_index_for_child,
47	request_session_info, request_submit_report_dispute_lost, request_unapplied_slashes,
48	request_unapplied_slashes_v2, request_validation_code_by_hash, request_validator_groups,
49};
50
51/// Errors that can happen on runtime fetches.
52mod error;
53
54use error::Result;
55pub use error::{recv_runtime, Error, FatalError, JfyiError};
56
57const LOG_TARGET: &'static str = "parachain::runtime-info";
58
59/// Configuration for construction a `RuntimeInfo`.
60pub struct Config {
61	/// Needed for retrieval of `ValidatorInfo`
62	///
63	/// Pass `None` if you are not interested.
64	pub keystore: Option<KeystorePtr>,
65
66	/// How many sessions should we keep in the cache?
67	pub session_cache_lru_size: u32,
68}
69
70/// Caching of session info.
71///
72/// It should be ensured that a cached session stays live in the cache as long as we might need it.
73pub struct RuntimeInfo {
74	/// Get the session index for a given relay parent.
75	///
76	/// We query this up to a 100 times per block, so caching it here without roundtrips over the
77	/// overseer seems sensible.
78	session_index_cache: LruMap<Hash, SessionIndex>,
79
80	/// In the happy case, we do not query disabled validators at all. In the worst case, we can
81	/// query it order of `n_cores` times `n_validators` per block, so caching it here seems
82	/// sensible.
83	disabled_validators_cache: LruMap<Hash, Vec<ValidatorIndex>>,
84
85	/// Look up cached sessions by `SessionIndex`.
86	session_info_cache: LruMap<SessionIndex, ExtendedSessionInfo>,
87
88	/// Unpin handle of *some* block in the session.
89	/// Only blocks pinned explicitly by `pin_block` are stored here.
90	pinned_blocks: LruMap<SessionIndex, UnpinHandle>,
91
92	/// Key store for determining whether we are a validator and what `ValidatorIndex` we have.
93	keystore: Option<KeystorePtr>,
94}
95
96/// `SessionInfo` with additional useful data for validator nodes.
97pub struct ExtendedSessionInfo {
98	/// Actual session info as fetched from the runtime.
99	pub session_info: SessionInfo,
100	/// Contains useful information about ourselves, in case this node is a validator.
101	pub validator_info: ValidatorInfo,
102	/// Node features
103	pub node_features: NodeFeatures,
104}
105
106/// Information about ourselves, in case we are an `Authority`.
107///
108/// This data is derived from the `SessionInfo` and our key as found in the keystore.
109pub struct ValidatorInfo {
110	/// The index this very validator has in `SessionInfo` vectors, if any.
111	pub our_index: Option<ValidatorIndex>,
112	/// The group we belong to, if any.
113	pub our_group: Option<GroupIndex>,
114}
115
116impl Default for Config {
117	fn default() -> Self {
118		Self {
119			keystore: None,
120			// Usually we need to cache the current and the last session.
121			session_cache_lru_size: 2,
122		}
123	}
124}
125
126impl RuntimeInfo {
127	/// Create a new `RuntimeInfo` for convenient runtime fetches.
128	pub fn new(keystore: Option<KeystorePtr>) -> Self {
129		Self::new_with_config(Config { keystore, ..Default::default() })
130	}
131
132	/// Create with more elaborate configuration options.
133	pub fn new_with_config(cfg: Config) -> Self {
134		Self {
135			// Usually messages are processed for blocks pointing to hashes from last finalized
136			// block to to best, so make this cache large enough to hold at least this amount of
137			// hashes, so that we get the benefit of caching even when finality lag is large.
138			session_index_cache: LruMap::new(ByLength::new(
139				cfg.session_cache_lru_size.max(2 * MAX_FINALITY_LAG),
140			)),
141			session_info_cache: LruMap::new(ByLength::new(cfg.session_cache_lru_size)),
142			disabled_validators_cache: LruMap::new(ByLength::new(100)),
143			pinned_blocks: LruMap::new(ByLength::new(cfg.session_cache_lru_size)),
144			keystore: cfg.keystore,
145		}
146	}
147
148	/// Returns the session index expected at any child of the `parent` block.
149	/// This does not return the session index for the `parent` block.
150	pub async fn get_session_index_for_child<Sender>(
151		&mut self,
152		sender: &mut Sender,
153		parent: Hash,
154	) -> Result<SessionIndex>
155	where
156		Sender: SubsystemSender<RuntimeApiMessage>,
157	{
158		match self.session_index_cache.get(&parent) {
159			Some(index) => Ok(*index),
160			None => {
161				let index =
162					recv_runtime(request_session_index_for_child(parent, sender).await).await?;
163				self.session_index_cache.insert(parent, index);
164				Ok(index)
165			},
166		}
167	}
168
169	/// Pin a given block in the given session if none are pinned in that session.
170	/// Unpinning will happen automatically when LRU cache grows over the limit.
171	pub fn pin_block(&mut self, session_index: SessionIndex, unpin_handle: UnpinHandle) {
172		self.pinned_blocks.get_or_insert(session_index, || unpin_handle);
173	}
174
175	/// Get the hash of a pinned block for the given session index, if any.
176	pub fn get_block_in_session(&self, session_index: SessionIndex) -> Option<Hash> {
177		self.pinned_blocks.peek(&session_index).map(|h| h.hash())
178	}
179
180	/// Get `ExtendedSessionInfo` by relay parent hash.
181	pub async fn get_session_info<'a, Sender>(
182		&'a mut self,
183		sender: &mut Sender,
184		relay_parent: Hash,
185	) -> Result<&'a ExtendedSessionInfo>
186	where
187		Sender: SubsystemSender<RuntimeApiMessage>,
188	{
189		let session_index = self.get_session_index_for_child(sender, relay_parent).await?;
190
191		self.get_session_info_by_index(sender, relay_parent, session_index).await
192	}
193
194	/// Get the list of disabled validators at the relay parent.
195	pub async fn get_disabled_validators<Sender>(
196		&mut self,
197		sender: &mut Sender,
198		relay_parent: Hash,
199	) -> Result<Vec<ValidatorIndex>>
200	where
201		Sender: SubsystemSender<RuntimeApiMessage>,
202	{
203		match self.disabled_validators_cache.get(&relay_parent).cloned() {
204			Some(result) => Ok(result),
205			None => {
206				let disabled_validators =
207					request_disabled_validators(relay_parent, sender).await.await??;
208				self.disabled_validators_cache.insert(relay_parent, disabled_validators.clone());
209				Ok(disabled_validators)
210			},
211		}
212	}
213
214	/// Get `ExtendedSessionInfo` by session index.
215	///
216	/// `request_session_info` still requires the parent to be passed in, so we take the parent
217	/// in addition to the `SessionIndex`.
218	pub async fn get_session_info_by_index<'a, Sender>(
219		&'a mut self,
220		sender: &mut Sender,
221		parent: Hash,
222		session_index: SessionIndex,
223	) -> Result<&'a ExtendedSessionInfo>
224	where
225		Sender: SubsystemSender<RuntimeApiMessage>,
226	{
227		if self.session_info_cache.get(&session_index).is_none() {
228			let session_info =
229				recv_runtime(request_session_info(parent, session_index, sender).await)
230					.await?
231					.ok_or(JfyiError::NoSuchSession(session_index))?;
232
233			let validator_info = self.get_validator_info(&session_info)?;
234
235			let node_features =
236				request_node_features(parent, session_index, sender).await.await??;
237			let last_set_index = node_features.iter_ones().last().unwrap_or_default();
238			if last_set_index >= FeatureIndex::FirstUnassigned as usize {
239				gum::warn!(target: LOG_TARGET, "Runtime requires feature bit {} that node doesn't support, please upgrade node version", last_set_index);
240			}
241
242			let full_info = ExtendedSessionInfo { session_info, validator_info, node_features };
243
244			self.session_info_cache.insert(session_index, full_info);
245		}
246		Ok(self
247			.session_info_cache
248			.get(&session_index)
249			.expect("We just put the value there. qed."))
250	}
251
252	/// Convenience function for checking the signature of something signed.
253	pub async fn check_signature<Sender, Payload, RealPayload>(
254		&mut self,
255		sender: &mut Sender,
256		relay_parent: Hash,
257		signed: UncheckedSigned<Payload, RealPayload>,
258	) -> Result<
259		std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>,
260	>
261	where
262		Sender: SubsystemSender<RuntimeApiMessage>,
263		Payload: EncodeAs<RealPayload> + Clone,
264		RealPayload: Encode + Clone,
265	{
266		let session_index = self.get_session_index_for_child(sender, relay_parent).await?;
267		let info = self.get_session_info_by_index(sender, relay_parent, session_index).await?;
268		Ok(check_signature(session_index, &info.session_info, relay_parent, signed))
269	}
270
271	/// Build `ValidatorInfo` for the current session.
272	///
273	///
274	/// Returns: `None` if not a parachain validator.
275	fn get_validator_info(&self, session_info: &SessionInfo) -> Result<ValidatorInfo> {
276		if let Some(our_index) = self.get_our_index(&session_info.validators) {
277			// Get our group index:
278			let our_group =
279				session_info.validator_groups.iter().enumerate().find_map(|(i, g)| {
280					g.iter().find_map(|v| {
281						if *v == our_index {
282							Some(GroupIndex(i as u32))
283						} else {
284							None
285						}
286					})
287				});
288			let info = ValidatorInfo { our_index: Some(our_index), our_group };
289			return Ok(info);
290		}
291		return Ok(ValidatorInfo { our_index: None, our_group: None });
292	}
293
294	/// Get our `ValidatorIndex`.
295	///
296	/// Returns: None if we are not a validator.
297	fn get_our_index(
298		&self,
299		validators: &IndexedVec<ValidatorIndex, ValidatorId>,
300	) -> Option<ValidatorIndex> {
301		let keystore = self.keystore.as_ref()?;
302		for (i, v) in validators.iter().enumerate() {
303			if Keystore::has_keys(&**keystore, &[(v.to_raw_vec(), ValidatorId::ID)]) {
304				return Some(ValidatorIndex(i as u32));
305			}
306		}
307		None
308	}
309}
310
311/// Convenience function for quickly checking the signature on signed data.
312pub fn check_signature<Payload, RealPayload>(
313	session_index: SessionIndex,
314	session_info: &SessionInfo,
315	relay_parent: Hash,
316	signed: UncheckedSigned<Payload, RealPayload>,
317) -> std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>
318where
319	Payload: EncodeAs<RealPayload> + Clone,
320	RealPayload: Encode + Clone,
321{
322	let signing_context = SigningContext { session_index, parent_hash: relay_parent };
323
324	session_info
325		.validators
326		.get(signed.unchecked_validator_index())
327		.ok_or_else(|| signed.clone())
328		.and_then(|v| signed.try_into_checked(&signing_context, v))
329}
330
331/// Request availability cores from the runtime.
332pub async fn get_availability_cores<Sender>(
333	sender: &mut Sender,
334	relay_parent: Hash,
335) -> Result<Vec<CoreState>>
336where
337	Sender: overseer::SubsystemSender<RuntimeApiMessage>,
338{
339	recv_runtime(request_availability_cores(relay_parent, sender).await).await
340}
341
342/// Variant of `request_availability_cores` that only returns occupied ones.
343pub async fn get_occupied_cores<Sender>(
344	sender: &mut Sender,
345	relay_parent: Hash,
346) -> Result<Vec<(CoreIndex, OccupiedCore)>>
347where
348	Sender: overseer::SubsystemSender<RuntimeApiMessage>,
349{
350	let cores = get_availability_cores(sender, relay_parent).await?;
351
352	Ok(cores
353		.into_iter()
354		.enumerate()
355		.filter_map(|(core_index, core_state)| {
356			if let CoreState::Occupied(occupied) = core_state {
357				Some((CoreIndex(core_index as u32), occupied))
358			} else {
359				None
360			}
361		})
362		.collect())
363}
364
365/// Get group rotation info based on the given `relay_parent`.
366pub async fn get_group_rotation_info<Sender>(
367	sender: &mut Sender,
368	relay_parent: Hash,
369) -> Result<GroupRotationInfo>
370where
371	Sender: overseer::SubsystemSender<RuntimeApiMessage>,
372{
373	// We drop `groups` here as we don't need them, because of `RuntimeInfo`. Ideally we would not
374	// fetch them in the first place.
375	let (_, info) = recv_runtime(request_validator_groups(relay_parent, sender).await).await?;
376	Ok(info)
377}
378
379/// Get `CandidateEvent`s for the given `relay_parent`.
380pub async fn get_candidate_events<Sender>(
381	sender: &mut Sender,
382	relay_parent: Hash,
383) -> Result<Vec<CandidateEvent>>
384where
385	Sender: SubsystemSender<RuntimeApiMessage>,
386{
387	recv_runtime(request_candidate_events(relay_parent, sender).await).await
388}
389
390/// Get on chain votes.
391pub async fn get_on_chain_votes<Sender>(
392	sender: &mut Sender,
393	relay_parent: Hash,
394) -> Result<Option<ScrapedOnChainVotes>>
395where
396	Sender: SubsystemSender<RuntimeApiMessage>,
397{
398	recv_runtime(request_on_chain_votes(relay_parent, sender).await).await
399}
400
401/// Fetch `ValidationCode` by hash from the runtime.
402pub async fn get_validation_code_by_hash<Sender>(
403	sender: &mut Sender,
404	relay_parent: Hash,
405	validation_code_hash: ValidationCodeHash,
406) -> Result<Option<ValidationCode>>
407where
408	Sender: SubsystemSender<RuntimeApiMessage>,
409{
410	recv_runtime(request_validation_code_by_hash(relay_parent, validation_code_hash, sender).await)
411		.await
412}
413
414/// Fetch a list of `PendingSlashes` from the runtime.
415/// Will fallback to `unapplied_slashes` if the runtime does not
416/// support `unapplied_slashes_v2`.
417pub async fn get_unapplied_slashes<Sender>(
418	sender: &mut Sender,
419	relay_parent: Hash,
420) -> Result<Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>>
421where
422	Sender: SubsystemSender<RuntimeApiMessage>,
423{
424	match recv_runtime(request_unapplied_slashes_v2(relay_parent, sender).await).await {
425		Ok(v2) => Ok(v2),
426		Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) => {
427			// Fallback to legacy unapplied_slashes
428			let legacy =
429				recv_runtime(request_unapplied_slashes(relay_parent, sender).await).await?;
430			// Convert legacy slashes to PendingSlashes
431			Ok(legacy
432				.into_iter()
433				.map(|(session, candidate_hash, legacy_slash)| {
434					(
435						session,
436						candidate_hash,
437						slashing::PendingSlashes {
438							keys: legacy_slash.keys,
439							kind: legacy_slash.kind.into(),
440						},
441					)
442				})
443				.collect())
444		},
445		Err(e) => Err(e),
446	}
447}
448
449/// Generate validator key ownership proof.
450///
451/// Note: The choice of `relay_parent` is important here, it needs to match
452/// the desired session index of the validator set in question.
453pub async fn key_ownership_proof<Sender>(
454	sender: &mut Sender,
455	relay_parent: Hash,
456	validator_id: ValidatorId,
457) -> Result<Option<slashing::OpaqueKeyOwnershipProof>>
458where
459	Sender: SubsystemSender<RuntimeApiMessage>,
460{
461	recv_runtime(request_key_ownership_proof(relay_parent, validator_id, sender).await).await
462}
463
464/// Submit a past-session dispute slashing report.
465pub async fn submit_report_dispute_lost<Sender>(
466	sender: &mut Sender,
467	relay_parent: Hash,
468	dispute_proof: slashing::DisputeProof,
469	key_ownership_proof: slashing::OpaqueKeyOwnershipProof,
470) -> Result<Option<()>>
471where
472	Sender: SubsystemSender<RuntimeApiMessage>,
473{
474	recv_runtime(
475		request_submit_report_dispute_lost(
476			relay_parent,
477			dispute_proof,
478			key_ownership_proof,
479			sender,
480		)
481		.await,
482	)
483	.await
484}
485
486/// A snapshot of the runtime claim queue at an arbitrary relay chain block.
487#[derive(Default, Clone, Debug)]
488pub struct ClaimQueueSnapshot(pub BTreeMap<CoreIndex, VecDeque<ParaId>>);
489
490impl From<BTreeMap<CoreIndex, VecDeque<ParaId>>> for ClaimQueueSnapshot {
491	fn from(claim_queue_snapshot: BTreeMap<CoreIndex, VecDeque<ParaId>>) -> Self {
492		ClaimQueueSnapshot(claim_queue_snapshot)
493	}
494}
495
496impl ClaimQueueSnapshot {
497	/// Returns the `ParaId` that has a claim for `core_index` at the specified `depth` in the
498	/// claim queue. A depth of `0` means the very next block.
499	pub fn get_claim_for(&self, core_index: CoreIndex, depth: usize) -> Option<ParaId> {
500		self.0.get(&core_index)?.get(depth).copied()
501	}
502
503	/// Returns an iterator over all claimed cores and the claiming `ParaId` at the specified
504	/// `depth` in the claim queue.
505	pub fn iter_claims_at_depth(
506		&self,
507		depth: usize,
508	) -> impl Iterator<Item = (CoreIndex, ParaId)> + '_ {
509		self.0
510			.iter()
511			.filter_map(move |(core_index, paras)| Some((*core_index, *paras.get(depth)?)))
512	}
513
514	/// Returns an iterator over all claims on the given core.
515	pub fn iter_claims_for_core(
516		&self,
517		core_index: &CoreIndex,
518	) -> impl Iterator<Item = &ParaId> + '_ {
519		self.0.get(core_index).map(|c| c.iter()).into_iter().flatten()
520	}
521
522	/// Returns an iterator over the whole claim queue.
523	pub fn iter_all_claims(&self) -> impl Iterator<Item = (&CoreIndex, &VecDeque<ParaId>)> + '_ {
524		self.0.iter()
525	}
526
527	/// Get all claimed cores for the given `para_id` at the specified depth.
528	pub fn iter_claims_at_depth_for_para(
529		&self,
530		depth: usize,
531		para_id: ParaId,
532	) -> impl Iterator<Item = CoreIndex> + '_ {
533		self.0.iter().filter_map(move |(core_index, ids)| {
534			ids.get(depth).filter(|id| **id == para_id).map(|_| *core_index)
535		})
536	}
537}
538
539/// Fetch the claim queue and wrap it into a helpful `ClaimQueueSnapshot`
540pub async fn fetch_claim_queue(
541	sender: &mut impl SubsystemSender<RuntimeApiMessage>,
542	relay_parent: Hash,
543) -> Result<ClaimQueueSnapshot> {
544	let cq = request_claim_queue(relay_parent, sender)
545		.await
546		.await
547		.map_err(Error::RuntimeRequestCanceled)??;
548
549	Ok(cq.into())
550}
551
552/// Returns the lookahead from the scheduler params if the runtime supports it,
553/// or default value if scheduling lookahead API is not supported by runtime.
554pub async fn fetch_scheduling_lookahead(
555	parent: Hash,
556	session_index: SessionIndex,
557	sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>,
558) -> Result<u32> {
559	let res = recv_runtime(
560		request_from_runtime(parent, sender, |tx| {
561			RuntimeApiRequest::SchedulingLookahead(session_index, tx)
562		})
563		.await,
564	)
565	.await;
566
567	if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res {
568		gum::trace!(
569			target: LOG_TARGET,
570			?parent,
571			"Querying the scheduling lookahead from the runtime is not supported by the current Runtime API, falling back to default value of {}",
572			DEFAULT_SCHEDULING_LOOKAHEAD
573		);
574
575		Ok(DEFAULT_SCHEDULING_LOOKAHEAD)
576	} else {
577		res
578	}
579}
580
581/// Fetch the validation code bomb limit from the runtime.
582pub async fn fetch_validation_code_bomb_limit(
583	parent: Hash,
584	session_index: SessionIndex,
585	sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>,
586) -> Result<u32> {
587	let res = recv_runtime(
588		request_from_runtime(parent, sender, |tx| {
589			RuntimeApiRequest::ValidationCodeBombLimit(session_index, tx)
590		})
591		.await,
592	)
593	.await;
594
595	if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res {
596		gum::trace!(
597			target: LOG_TARGET,
598			?parent,
599			"Querying the validation code bomb limit from the runtime is not supported by the current Runtime API",
600		);
601
602		// TODO: Remove this once runtime API version 12 is released.
603		#[allow(deprecated)]
604		Ok(polkadot_node_primitives::VALIDATION_CODE_BOMB_LIMIT as u32)
605	} else {
606		res
607	}
608}