1use 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
51mod error;
53
54use error::Result;
55pub use error::{recv_runtime, Error, FatalError, JfyiError};
56
57const LOG_TARGET: &'static str = "parachain::runtime-info";
58
59pub struct Config {
61 pub keystore: Option<KeystorePtr>,
65
66 pub session_cache_lru_size: u32,
68}
69
70pub struct RuntimeInfo {
74 session_index_cache: LruMap<Hash, SessionIndex>,
79
80 disabled_validators_cache: LruMap<Hash, Vec<ValidatorIndex>>,
84
85 session_info_cache: LruMap<SessionIndex, ExtendedSessionInfo>,
87
88 pinned_blocks: LruMap<SessionIndex, UnpinHandle>,
91
92 keystore: Option<KeystorePtr>,
94}
95
96pub struct ExtendedSessionInfo {
98 pub session_info: SessionInfo,
100 pub validator_info: ValidatorInfo,
102 pub node_features: NodeFeatures,
104}
105
106pub struct ValidatorInfo {
110 pub our_index: Option<ValidatorIndex>,
112 pub our_group: Option<GroupIndex>,
114}
115
116impl Default for Config {
117 fn default() -> Self {
118 Self {
119 keystore: None,
120 session_cache_lru_size: 2,
122 }
123 }
124}
125
126impl RuntimeInfo {
127 pub fn new(keystore: Option<KeystorePtr>) -> Self {
129 Self::new_with_config(Config { keystore, ..Default::default() })
130 }
131
132 pub fn new_with_config(cfg: Config) -> Self {
134 Self {
135 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 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 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 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 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 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 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 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 fn get_validator_info(&self, session_info: &SessionInfo) -> Result<ValidatorInfo> {
276 if let Some(our_index) = self.get_our_index(&session_info.validators) {
277 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 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
311pub 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
331pub 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
342pub 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
365pub 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 let (_, info) = recv_runtime(request_validator_groups(relay_parent, sender).await).await?;
376 Ok(info)
377}
378
379pub 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
390pub 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
401pub 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
414pub 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 let legacy =
429 recv_runtime(request_unapplied_slashes(relay_parent, sender).await).await?;
430 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
449pub 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
464pub 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#[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 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 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 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 pub fn iter_all_claims(&self) -> impl Iterator<Item = (&CoreIndex, &VecDeque<ParaId>)> + '_ {
524 self.0.iter()
525 }
526
527 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
539pub 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
552pub 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
581pub 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 #[allow(deprecated)]
604 Ok(polkadot_node_primitives::VALIDATION_CODE_BOMB_LIMIT as u32)
605 } else {
606 res
607 }
608}