// Copyright 2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! Convenient interface to runtime information. use std::cmp::max; use lru::LruCache; use parity_scale_codec::Encode; use sp_application_crypto::AppKey; use sp_core::crypto::Public; use sp_keystore::{CryptoStore, SyncCryptoStorePtr}; use polkadot_node_subsystem::{SubsystemContext, SubsystemSender}; use polkadot_primitives::v1::{ CoreState, EncodeAs, GroupIndex, GroupRotationInfo, Hash, OccupiedCore, SessionIndex, SessionInfo, Signed, SigningContext, UncheckedSigned, ValidatorId, ValidatorIndex, }; use crate::{ request_availability_cores, request_session_index_for_child, request_session_info, request_validator_groups, }; /// Errors that can happen on runtime fetches. mod error; use error::{recv_runtime, Result}; pub use error::{Error, Fatal, NonFatal}; /// Configuration for construction a `RuntimeInfo`. pub struct Config { /// Needed for retrieval of `ValidatorInfo` /// /// Pass `None` if you are not interested. pub keystore: Option, /// How many sessions should we keep in the cache? pub session_cache_lru_size: usize, } /// Caching of session info. /// /// It should be ensured that a cached session stays live in the cache as long as we might need it. pub struct RuntimeInfo { /// Get the session index for a given relay parent. /// /// We query this up to a 100 times per block, so caching it here without roundtrips over the /// overseer seems sensible. session_index_cache: LruCache, /// Look up cached sessions by `SessionIndex`. session_info_cache: LruCache, /// Key store for determining whether we are a validator and what `ValidatorIndex` we have. keystore: Option, } /// `SessionInfo` with additional useful data for validator nodes. pub struct ExtendedSessionInfo { /// Actual session info as fetched from the runtime. pub session_info: SessionInfo, /// Contains useful information about ourselves, in case this node is a validator. pub validator_info: ValidatorInfo, } /// Information about ourselves, in case we are an `Authority`. /// /// This data is derived from the `SessionInfo` and our key as found in the keystore. pub struct ValidatorInfo { /// The index this very validator has in `SessionInfo` vectors, if any. pub our_index: Option, /// The group we belong to, if any. pub our_group: Option, } impl Default for Config { fn default() -> Self { Self { keystore: None, // Usually we need to cache the current and the last session. session_cache_lru_size: 2, } } } impl RuntimeInfo { /// Create a new `RuntimeInfo` for convenient runtime fetches. pub fn new(keystore: Option) -> Self { Self::new_with_config(Config { keystore, ..Default::default() }) } /// Create with more elaborate configuration options. pub fn new_with_config(cfg: Config) -> Self { Self { session_index_cache: LruCache::new(max(10, cfg.session_cache_lru_size)), session_info_cache: LruCache::new(cfg.session_cache_lru_size), keystore: cfg.keystore, } } /// Retrieve the current session index. pub async fn get_session_index( &mut self, sender: &mut Sender, parent: Hash, ) -> Result where Sender: SubsystemSender, { match self.session_index_cache.get(&parent) { Some(index) => Ok(*index), None => { let index = recv_runtime(request_session_index_for_child(parent, sender).await).await?; self.session_index_cache.put(parent, index); Ok(index) }, } } /// Get `ExtendedSessionInfo` by relay parent hash. pub async fn get_session_info<'a, Sender>( &'a mut self, sender: &mut Sender, parent: Hash, ) -> Result<&'a ExtendedSessionInfo> where Sender: SubsystemSender, { let session_index = self.get_session_index(sender, parent).await?; self.get_session_info_by_index(sender, parent, session_index).await } /// Get `ExtendedSessionInfo` by session index. /// /// `request_session_info` still requires the parent to be passed in, so we take the parent /// in addition to the `SessionIndex`. pub async fn get_session_info_by_index<'a, Sender>( &'a mut self, sender: &mut Sender, parent: Hash, session_index: SessionIndex, ) -> Result<&'a ExtendedSessionInfo> where Sender: SubsystemSender, { if !self.session_info_cache.contains(&session_index) { let session_info = recv_runtime(request_session_info(parent, session_index, sender).await) .await? .ok_or(NonFatal::NoSuchSession(session_index))?; let validator_info = self.get_validator_info(&session_info).await?; let full_info = ExtendedSessionInfo { session_info, validator_info }; self.session_info_cache.put(session_index, full_info); } Ok(self .session_info_cache .get(&session_index) .expect("We just put the value there. qed.")) } /// Convenience function for checking the signature of something signed. pub async fn check_signature( &mut self, sender: &mut Sender, parent: Hash, signed: UncheckedSigned, ) -> Result< std::result::Result, UncheckedSigned>, > where Sender: SubsystemSender, Payload: EncodeAs + Clone, RealPayload: Encode + Clone, { let session_index = self.get_session_index(sender, parent).await?; let info = self.get_session_info_by_index(sender, parent, session_index).await?; Ok(check_signature(session_index, &info.session_info, parent, signed)) } /// Build `ValidatorInfo` for the current session. /// /// /// Returns: `None` if not a parachain validator. async fn get_validator_info(&self, session_info: &SessionInfo) -> Result { if let Some(our_index) = self.get_our_index(&session_info.validators).await { // Get our group index: let our_group = session_info.validator_groups.iter().enumerate().find_map(|(i, g)| { g.iter().find_map(|v| { if *v == our_index { Some(GroupIndex(i as u32)) } else { None } }) }); let info = ValidatorInfo { our_index: Some(our_index), our_group }; return Ok(info) } return Ok(ValidatorInfo { our_index: None, our_group: None }) } /// Get our `ValidatorIndex`. /// /// Returns: None if we are not a validator. async fn get_our_index(&self, validators: &[ValidatorId]) -> Option { let keystore = self.keystore.as_ref()?; for (i, v) in validators.iter().enumerate() { if CryptoStore::has_keys(&**keystore, &[(v.to_raw_vec(), ValidatorId::ID)]).await { return Some(ValidatorIndex(i as u32)) } } None } } /// Convenience function for quickly checking the signature on signed data. pub fn check_signature( session_index: SessionIndex, session_info: &SessionInfo, relay_parent: Hash, signed: UncheckedSigned, ) -> std::result::Result, UncheckedSigned> where Payload: EncodeAs + Clone, RealPayload: Encode + Clone, { let signing_context = SigningContext { session_index, parent_hash: relay_parent }; session_info .validators .get(signed.unchecked_validator_index().0 as usize) .ok_or_else(|| signed.clone()) .and_then(|v| signed.try_into_checked(&signing_context, v)) } /// Request availability cores from the runtime. pub async fn get_availability_cores( ctx: &mut Context, relay_parent: Hash, ) -> Result> where Context: SubsystemContext, { recv_runtime(request_availability_cores(relay_parent, ctx.sender()).await).await } /// Variant of `request_availability_cores` that only returns occupied ones. pub async fn get_occupied_cores( ctx: &mut Context, relay_parent: Hash, ) -> Result> where Context: SubsystemContext, { let cores = get_availability_cores(ctx, relay_parent).await?; Ok(cores .into_iter() .filter_map(|core_state| { if let CoreState::Occupied(occupied) = core_state { Some(occupied) } else { None } }) .collect()) } /// Get group rotation info based on the given `relay_parent`. pub async fn get_group_rotation_info( ctx: &mut Context, relay_parent: Hash, ) -> Result where Context: SubsystemContext, { // We drop `groups` here as we don't need them, because of `RuntimeInfo`. Ideally we would not // fetch them in the first place. let (_, info) = recv_runtime(request_validator_groups(relay_parent, ctx.sender()).await).await?; Ok(info) }