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, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, IndexedVec,
36 NodeFeatures, OccupiedCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed,
37 SigningContext, UncheckedSigned, ValidationCode, ValidationCodeHash, ValidatorId,
38 ValidatorIndex, 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_executor_params,
47 request_session_index_for_child, request_session_info, request_submit_report_dispute_lost,
48 request_unapplied_slashes, request_unapplied_slashes_v2, request_validation_code_by_hash,
49 request_validator_groups,
50};
51
52mod error;
54
55use error::Result;
56pub use error::{recv_runtime, Error, FatalError, JfyiError};
57
58const LOG_TARGET: &'static str = "parachain::runtime-info";
59
60pub struct Config {
62 pub keystore: Option<KeystorePtr>,
66
67 pub session_cache_lru_size: u32,
69}
70
71pub struct RuntimeInfo {
75 session_index_cache: LruMap<Hash, SessionIndex>,
80
81 disabled_validators_cache: LruMap<Hash, Vec<ValidatorIndex>>,
85
86 session_info_cache: LruMap<SessionIndex, ExtendedSessionInfo>,
88
89 pinned_blocks: LruMap<SessionIndex, UnpinHandle>,
92
93 keystore: Option<KeystorePtr>,
95}
96
97pub struct ExtendedSessionInfo {
99 pub session_info: SessionInfo,
101 pub validator_info: ValidatorInfo,
103 pub executor_params: ExecutorParams,
105 pub node_features: NodeFeatures,
107}
108
109pub struct ValidatorInfo {
113 pub our_index: Option<ValidatorIndex>,
115 pub our_group: Option<GroupIndex>,
117}
118
119impl Default for Config {
120 fn default() -> Self {
121 Self {
122 keystore: None,
123 session_cache_lru_size: 2,
125 }
126 }
127}
128
129impl RuntimeInfo {
130 pub fn new(keystore: Option<KeystorePtr>) -> Self {
132 Self::new_with_config(Config { keystore, ..Default::default() })
133 }
134
135 pub fn new_with_config(cfg: Config) -> Self {
137 Self {
138 session_index_cache: LruMap::new(ByLength::new(
142 cfg.session_cache_lru_size.max(2 * MAX_FINALITY_LAG),
143 )),
144 session_info_cache: LruMap::new(ByLength::new(cfg.session_cache_lru_size)),
145 disabled_validators_cache: LruMap::new(ByLength::new(100)),
146 pinned_blocks: LruMap::new(ByLength::new(cfg.session_cache_lru_size)),
147 keystore: cfg.keystore,
148 }
149 }
150
151 pub async fn get_session_index_for_child<Sender>(
154 &mut self,
155 sender: &mut Sender,
156 parent: Hash,
157 ) -> Result<SessionIndex>
158 where
159 Sender: SubsystemSender<RuntimeApiMessage>,
160 {
161 match self.session_index_cache.get(&parent) {
162 Some(index) => Ok(*index),
163 None => {
164 let index =
165 recv_runtime(request_session_index_for_child(parent, sender).await).await?;
166 self.session_index_cache.insert(parent, index);
167 Ok(index)
168 },
169 }
170 }
171
172 pub fn pin_block(&mut self, session_index: SessionIndex, unpin_handle: UnpinHandle) {
175 self.pinned_blocks.get_or_insert(session_index, || unpin_handle);
176 }
177
178 pub fn get_block_in_session(&self, session_index: SessionIndex) -> Option<Hash> {
180 self.pinned_blocks.peek(&session_index).map(|h| h.hash())
181 }
182
183 pub async fn get_session_info<'a, Sender>(
185 &'a mut self,
186 sender: &mut Sender,
187 relay_parent: Hash,
188 ) -> Result<&'a ExtendedSessionInfo>
189 where
190 Sender: SubsystemSender<RuntimeApiMessage>,
191 {
192 let session_index = self.get_session_index_for_child(sender, relay_parent).await?;
193
194 self.get_session_info_by_index(sender, relay_parent, session_index).await
195 }
196
197 pub async fn get_disabled_validators<Sender>(
199 &mut self,
200 sender: &mut Sender,
201 relay_parent: Hash,
202 ) -> Result<Vec<ValidatorIndex>>
203 where
204 Sender: SubsystemSender<RuntimeApiMessage>,
205 {
206 match self.disabled_validators_cache.get(&relay_parent).cloned() {
207 Some(result) => Ok(result),
208 None => {
209 let disabled_validators =
210 request_disabled_validators(relay_parent, sender).await.await??;
211 self.disabled_validators_cache.insert(relay_parent, disabled_validators.clone());
212 Ok(disabled_validators)
213 },
214 }
215 }
216
217 pub async fn get_session_info_by_index<'a, Sender>(
222 &'a mut self,
223 sender: &mut Sender,
224 parent: Hash,
225 session_index: SessionIndex,
226 ) -> Result<&'a ExtendedSessionInfo>
227 where
228 Sender: SubsystemSender<RuntimeApiMessage>,
229 {
230 if self.session_info_cache.get(&session_index).is_none() {
231 let session_info =
232 recv_runtime(request_session_info(parent, session_index, sender).await)
233 .await?
234 .ok_or(JfyiError::NoSuchSession(session_index))?;
235
236 let executor_params =
237 recv_runtime(request_session_executor_params(parent, session_index, sender).await)
238 .await?
239 .ok_or(JfyiError::NoExecutorParams(session_index))?;
240
241 let validator_info = self.get_validator_info(&session_info)?;
242
243 let node_features =
244 request_node_features(parent, session_index, sender).await.await??;
245 let last_set_index = node_features.iter_ones().last().unwrap_or_default();
246 if last_set_index >= FeatureIndex::FirstUnassigned as usize {
247 gum::warn!(target: LOG_TARGET, "Runtime requires feature bit {} that node doesn't support, please upgrade node version", last_set_index);
248 }
249
250 let full_info = ExtendedSessionInfo {
251 session_info,
252 validator_info,
253 executor_params,
254 node_features,
255 };
256
257 self.session_info_cache.insert(session_index, full_info);
258 }
259 Ok(self
260 .session_info_cache
261 .get(&session_index)
262 .expect("We just put the value there. qed."))
263 }
264
265 pub async fn check_signature<Sender, Payload, RealPayload>(
267 &mut self,
268 sender: &mut Sender,
269 relay_parent: Hash,
270 signed: UncheckedSigned<Payload, RealPayload>,
271 ) -> Result<
272 std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>,
273 >
274 where
275 Sender: SubsystemSender<RuntimeApiMessage>,
276 Payload: EncodeAs<RealPayload> + Clone,
277 RealPayload: Encode + Clone,
278 {
279 let session_index = self.get_session_index_for_child(sender, relay_parent).await?;
280 let info = self.get_session_info_by_index(sender, relay_parent, session_index).await?;
281 Ok(check_signature(session_index, &info.session_info, relay_parent, signed))
282 }
283
284 fn get_validator_info(&self, session_info: &SessionInfo) -> Result<ValidatorInfo> {
289 if let Some(our_index) = self.get_our_index(&session_info.validators) {
290 let our_group =
292 session_info.validator_groups.iter().enumerate().find_map(|(i, g)| {
293 g.iter().find_map(|v| {
294 if *v == our_index {
295 Some(GroupIndex(i as u32))
296 } else {
297 None
298 }
299 })
300 });
301 let info = ValidatorInfo { our_index: Some(our_index), our_group };
302 return Ok(info)
303 }
304 return Ok(ValidatorInfo { our_index: None, our_group: None })
305 }
306
307 fn get_our_index(
311 &self,
312 validators: &IndexedVec<ValidatorIndex, ValidatorId>,
313 ) -> Option<ValidatorIndex> {
314 let keystore = self.keystore.as_ref()?;
315 for (i, v) in validators.iter().enumerate() {
316 if Keystore::has_keys(&**keystore, &[(v.to_raw_vec(), ValidatorId::ID)]) {
317 return Some(ValidatorIndex(i as u32))
318 }
319 }
320 None
321 }
322}
323
324pub fn check_signature<Payload, RealPayload>(
326 session_index: SessionIndex,
327 session_info: &SessionInfo,
328 relay_parent: Hash,
329 signed: UncheckedSigned<Payload, RealPayload>,
330) -> std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>
331where
332 Payload: EncodeAs<RealPayload> + Clone,
333 RealPayload: Encode + Clone,
334{
335 let signing_context = SigningContext { session_index, parent_hash: relay_parent };
336
337 session_info
338 .validators
339 .get(signed.unchecked_validator_index())
340 .ok_or_else(|| signed.clone())
341 .and_then(|v| signed.try_into_checked(&signing_context, v))
342}
343
344pub async fn get_availability_cores<Sender>(
346 sender: &mut Sender,
347 relay_parent: Hash,
348) -> Result<Vec<CoreState>>
349where
350 Sender: overseer::SubsystemSender<RuntimeApiMessage>,
351{
352 recv_runtime(request_availability_cores(relay_parent, sender).await).await
353}
354
355pub async fn get_occupied_cores<Sender>(
357 sender: &mut Sender,
358 relay_parent: Hash,
359) -> Result<Vec<(CoreIndex, OccupiedCore)>>
360where
361 Sender: overseer::SubsystemSender<RuntimeApiMessage>,
362{
363 let cores = get_availability_cores(sender, relay_parent).await?;
364
365 Ok(cores
366 .into_iter()
367 .enumerate()
368 .filter_map(|(core_index, core_state)| {
369 if let CoreState::Occupied(occupied) = core_state {
370 Some((CoreIndex(core_index as u32), occupied))
371 } else {
372 None
373 }
374 })
375 .collect())
376}
377
378pub async fn get_group_rotation_info<Sender>(
380 sender: &mut Sender,
381 relay_parent: Hash,
382) -> Result<GroupRotationInfo>
383where
384 Sender: overseer::SubsystemSender<RuntimeApiMessage>,
385{
386 let (_, info) = recv_runtime(request_validator_groups(relay_parent, sender).await).await?;
389 Ok(info)
390}
391
392pub async fn get_candidate_events<Sender>(
394 sender: &mut Sender,
395 relay_parent: Hash,
396) -> Result<Vec<CandidateEvent>>
397where
398 Sender: SubsystemSender<RuntimeApiMessage>,
399{
400 recv_runtime(request_candidate_events(relay_parent, sender).await).await
401}
402
403pub async fn get_on_chain_votes<Sender>(
405 sender: &mut Sender,
406 relay_parent: Hash,
407) -> Result<Option<ScrapedOnChainVotes>>
408where
409 Sender: SubsystemSender<RuntimeApiMessage>,
410{
411 recv_runtime(request_on_chain_votes(relay_parent, sender).await).await
412}
413
414pub async fn get_validation_code_by_hash<Sender>(
416 sender: &mut Sender,
417 relay_parent: Hash,
418 validation_code_hash: ValidationCodeHash,
419) -> Result<Option<ValidationCode>>
420where
421 Sender: SubsystemSender<RuntimeApiMessage>,
422{
423 recv_runtime(request_validation_code_by_hash(relay_parent, validation_code_hash, sender).await)
424 .await
425}
426
427pub async fn get_unapplied_slashes<Sender>(
431 sender: &mut Sender,
432 relay_parent: Hash,
433) -> Result<Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>>
434where
435 Sender: SubsystemSender<RuntimeApiMessage>,
436{
437 match recv_runtime(request_unapplied_slashes_v2(relay_parent, sender).await).await {
438 Ok(v2) => Ok(v2),
439 Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) => {
440 let legacy =
442 recv_runtime(request_unapplied_slashes(relay_parent, sender).await).await?;
443 Ok(legacy
445 .into_iter()
446 .map(|(session, candidate_hash, legacy_slash)| {
447 (
448 session,
449 candidate_hash,
450 slashing::PendingSlashes {
451 keys: legacy_slash.keys,
452 kind: legacy_slash.kind.into(),
453 },
454 )
455 })
456 .collect())
457 },
458 Err(e) => Err(e),
459 }
460}
461
462pub async fn key_ownership_proof<Sender>(
467 sender: &mut Sender,
468 relay_parent: Hash,
469 validator_id: ValidatorId,
470) -> Result<Option<slashing::OpaqueKeyOwnershipProof>>
471where
472 Sender: SubsystemSender<RuntimeApiMessage>,
473{
474 recv_runtime(request_key_ownership_proof(relay_parent, validator_id, sender).await).await
475}
476
477pub async fn submit_report_dispute_lost<Sender>(
479 sender: &mut Sender,
480 relay_parent: Hash,
481 dispute_proof: slashing::DisputeProof,
482 key_ownership_proof: slashing::OpaqueKeyOwnershipProof,
483) -> Result<Option<()>>
484where
485 Sender: SubsystemSender<RuntimeApiMessage>,
486{
487 recv_runtime(
488 request_submit_report_dispute_lost(
489 relay_parent,
490 dispute_proof,
491 key_ownership_proof,
492 sender,
493 )
494 .await,
495 )
496 .await
497}
498
499#[derive(Default, Clone, Debug)]
501pub struct ClaimQueueSnapshot(pub BTreeMap<CoreIndex, VecDeque<ParaId>>);
502
503impl From<BTreeMap<CoreIndex, VecDeque<ParaId>>> for ClaimQueueSnapshot {
504 fn from(claim_queue_snapshot: BTreeMap<CoreIndex, VecDeque<ParaId>>) -> Self {
505 ClaimQueueSnapshot(claim_queue_snapshot)
506 }
507}
508
509impl ClaimQueueSnapshot {
510 pub fn get_claim_for(&self, core_index: CoreIndex, depth: usize) -> Option<ParaId> {
513 self.0.get(&core_index)?.get(depth).copied()
514 }
515
516 pub fn iter_claims_at_depth(
519 &self,
520 depth: usize,
521 ) -> impl Iterator<Item = (CoreIndex, ParaId)> + '_ {
522 self.0
523 .iter()
524 .filter_map(move |(core_index, paras)| Some((*core_index, *paras.get(depth)?)))
525 }
526
527 pub fn iter_claims_for_core(
529 &self,
530 core_index: &CoreIndex,
531 ) -> impl Iterator<Item = &ParaId> + '_ {
532 self.0.get(core_index).map(|c| c.iter()).into_iter().flatten()
533 }
534
535 pub fn iter_all_claims(&self) -> impl Iterator<Item = (&CoreIndex, &VecDeque<ParaId>)> + '_ {
537 self.0.iter()
538 }
539
540 pub fn iter_claims_at_depth_for_para(
542 &self,
543 depth: usize,
544 para_id: ParaId,
545 ) -> impl Iterator<Item = CoreIndex> + '_ {
546 self.0.iter().filter_map(move |(core_index, ids)| {
547 ids.get(depth).filter(|id| **id == para_id).map(|_| *core_index)
548 })
549 }
550}
551
552pub async fn fetch_claim_queue(
554 sender: &mut impl SubsystemSender<RuntimeApiMessage>,
555 relay_parent: Hash,
556) -> Result<ClaimQueueSnapshot> {
557 let cq = request_claim_queue(relay_parent, sender)
558 .await
559 .await
560 .map_err(Error::RuntimeRequestCanceled)??;
561
562 Ok(cq.into())
563}
564
565pub async fn fetch_scheduling_lookahead(
568 parent: Hash,
569 session_index: SessionIndex,
570 sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>,
571) -> Result<u32> {
572 let res = recv_runtime(
573 request_from_runtime(parent, sender, |tx| {
574 RuntimeApiRequest::SchedulingLookahead(session_index, tx)
575 })
576 .await,
577 )
578 .await;
579
580 if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res {
581 gum::trace!(
582 target: LOG_TARGET,
583 ?parent,
584 "Querying the scheduling lookahead from the runtime is not supported by the current Runtime API, falling back to default value of {}",
585 DEFAULT_SCHEDULING_LOOKAHEAD
586 );
587
588 Ok(DEFAULT_SCHEDULING_LOOKAHEAD)
589 } else {
590 res
591 }
592}
593
594pub async fn fetch_validation_code_bomb_limit(
596 parent: Hash,
597 session_index: SessionIndex,
598 sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>,
599) -> Result<u32> {
600 let res = recv_runtime(
601 request_from_runtime(parent, sender, |tx| {
602 RuntimeApiRequest::ValidationCodeBombLimit(session_index, tx)
603 })
604 .await,
605 )
606 .await;
607
608 if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res {
609 gum::trace!(
610 target: LOG_TARGET,
611 ?parent,
612 "Querying the validation code bomb limit from the runtime is not supported by the current Runtime API",
613 );
614
615 #[allow(deprecated)]
617 Ok(polkadot_node_primitives::VALIDATION_CODE_BOMB_LIMIT as u32)
618 } else {
619 res
620 }
621}
622
623#[cfg(test)]
624mod test {
625 use super::*;
626
627 #[test]
628 fn iter_claims_at_depth_for_para_works() {
629 let claim_queue = ClaimQueueSnapshot(BTreeMap::from_iter(
630 [
631 (
632 CoreIndex(0),
633 VecDeque::from_iter([ParaId::from(1), ParaId::from(2), ParaId::from(1)]),
634 ),
635 (
636 CoreIndex(1),
637 VecDeque::from_iter([ParaId::from(1), ParaId::from(1), ParaId::from(2)]),
638 ),
639 (
640 CoreIndex(2),
641 VecDeque::from_iter([ParaId::from(1), ParaId::from(2), ParaId::from(3)]),
642 ),
643 (
644 CoreIndex(3),
645 VecDeque::from_iter([ParaId::from(2), ParaId::from(1), ParaId::from(3)]),
646 ),
647 ]
648 .into_iter(),
649 ));
650
651 let depth_0_cores =
653 claim_queue.iter_claims_at_depth_for_para(0, 1u32.into()).collect::<Vec<_>>();
654 assert_eq!(depth_0_cores.len(), 3);
655 assert_eq!(depth_0_cores, vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]);
656
657 let depth_1_cores =
659 claim_queue.iter_claims_at_depth_for_para(1, 1u32.into()).collect::<Vec<_>>();
660 assert_eq!(depth_1_cores.len(), 2);
661 assert_eq!(depth_1_cores, vec![CoreIndex(1), CoreIndex(3)]);
662
663 let depth_2_cores =
665 claim_queue.iter_claims_at_depth_for_para(2, 1u32.into()).collect::<Vec<_>>();
666 assert_eq!(depth_2_cores.len(), 1);
667 assert_eq!(depth_2_cores, vec![CoreIndex(0)]);
668
669 let depth_3_cores =
671 claim_queue.iter_claims_at_depth_for_para(3, 1u32.into()).collect::<Vec<_>>();
672 assert!(depth_3_cores.is_empty());
673
674 let depth_0_cores =
676 claim_queue.iter_claims_at_depth_for_para(0, 2u32.into()).collect::<Vec<_>>();
677 assert_eq!(depth_0_cores.len(), 1);
678 assert_eq!(depth_0_cores, vec![CoreIndex(3)]);
679
680 let depth_1_cores =
682 claim_queue.iter_claims_at_depth_for_para(1, 2u32.into()).collect::<Vec<_>>();
683 assert_eq!(depth_1_cores.len(), 2);
684 assert_eq!(depth_1_cores, vec![CoreIndex(0), CoreIndex(2)]);
685
686 let depth_2_cores =
688 claim_queue.iter_claims_at_depth_for_para(2, 2u32.into()).collect::<Vec<_>>();
689 assert_eq!(depth_2_cores.len(), 1);
690 assert_eq!(depth_2_cores, vec![CoreIndex(1)]);
691
692 let depth_0_cores =
694 claim_queue.iter_claims_at_depth_for_para(0, 3u32.into()).collect::<Vec<_>>();
695 assert!(depth_0_cores.is_empty());
696
697 let depth_1_cores =
699 claim_queue.iter_claims_at_depth_for_para(1, 3u32.into()).collect::<Vec<_>>();
700 assert!(depth_1_cores.is_empty());
701
702 let depth_2_cores =
704 claim_queue.iter_claims_at_depth_for_para(2, 3u32.into()).collect::<Vec<_>>();
705 assert_eq!(depth_2_cores.len(), 2);
706 assert_eq!(depth_2_cores, vec![CoreIndex(2), CoreIndex(3)]);
707
708 let depth_0_cores =
710 claim_queue.iter_claims_at_depth_for_para(0, 99u32.into()).collect::<Vec<_>>();
711 assert!(depth_0_cores.is_empty());
712 }
713}