sp_session_validator_management_query/
lib.rs

1//! Queries for committee selection
2#![deny(missing_docs)]
3pub mod commands;
4pub mod get_registrations;
5pub mod types;
6
7use async_trait::async_trait;
8use authority_selection_inherents::{AuthoritySelectionDataSource, CandidateValidationApi};
9use derive_new::new;
10use parity_scale_codec::{Decode, Encode};
11use sidechain_block_search::{FindSidechainBlock, SidechainInfo, predicates::AnyBlockInEpoch};
12use sidechain_domain::{McEpochNumber, ScEpochNumber, StakePoolPublicKey};
13use sp_api::{ApiExt, ProvideRuntimeApi};
14use sp_blockchain::{HeaderBackend, Info};
15use sp_core::bytes::to_hex;
16use sp_runtime::traits::NumberFor;
17use sp_runtime::traits::{Block as BlockT, Zero};
18use sp_session_validator_management::SessionValidatorManagementApi;
19#[allow(deprecated)]
20use sp_sidechain::{GetGenesisUtxo, GetSidechainStatus};
21use std::sync::Arc;
22use types::*;
23
24#[cfg(test)]
25mod tests;
26
27/// Result type for queries
28pub type QueryResult<T> = Result<T, String>;
29
30#[async_trait]
31/// API for Session Validator Management Queries
32pub trait SessionValidatorManagementQueryApi {
33	/// Returns the committee for given sidechain epoch. The order of the list represents the order of slot allocation.
34	fn get_epoch_committee(&self, epoch_number: u64) -> QueryResult<GetCommitteeResponse>;
35
36	///
37	/// returns: Last active and valid registration followed by all newer invalid registrations for mc_epoch_number and stake_pool_public_key.
38	/// Regardless of `mc_epoch_number` value, it always uses validation api from the latest sidechain block.
39	///
40	async fn get_registrations(
41		&self,
42		mc_epoch_number: McEpochNumber,
43		stake_pool_public_key: StakePoolPublicKey,
44	) -> QueryResult<Vec<CandidateRegistrationEntry>>;
45
46	/// Regardless of `epoch_number` value, all the candidates data validation is done based on the validation api from the latest sidechain block.
47	async fn get_ariadne_parameters(
48		&self,
49		epoch_number: McEpochNumber,
50	) -> QueryResult<AriadneParameters>;
51}
52
53#[derive(new)]
54/// Session Validator Management Query type wrapping client, and data source
55pub struct SessionValidatorManagementQuery<C, Block, AuthorityId: Decode, AuthorityKeys: Decode> {
56	client: Arc<C>,
57	candidate_data_source: Arc<dyn AuthoritySelectionDataSource + Send + Sync>,
58	_marker: std::marker::PhantomData<(Block, AuthorityId, AuthorityKeys)>,
59}
60
61impl<C, Block, AuthorityId, AuthorityKeys>
62	SessionValidatorManagementQuery<C, Block, AuthorityId, AuthorityKeys>
63where
64	Block: BlockT,
65	C: ProvideRuntimeApi<Block>,
66	C::Api: sp_api::Core<Block> + ApiExt<Block>,
67	AuthorityId: Encode + Decode + AsRef<[u8]> + Clone,
68	AuthorityKeys: Encode + Decode,
69	C::Api: SessionValidatorManagementApi<Block, AuthorityId, AuthorityKeys, ScEpochNumber>,
70{
71	fn validator_management_api_version(&self, block: Block::Hash) -> QueryResult<u32> {
72		let version = (self.client.runtime_api())
73			.api_version::<dyn SessionValidatorManagementApi<
74					Block,
75					AuthorityId,
76					AuthorityKeys,
77					ScEpochNumber,
78				>>(block)
79			.map_err(err_debug)?
80			.unwrap_or(1);
81		Ok(version)
82	}
83
84	fn get_current_committee_versioned(
85		&self,
86		block: Block::Hash,
87	) -> QueryResult<GetCommitteeResponse> {
88		let api = self.client.runtime_api();
89
90		if self.validator_management_api_version(block)? < 2 {
91			#[allow(deprecated)]
92			let (epoch, authorities) =
93				api.get_current_committee_before_version_2(block).map_err(err_debug)?;
94			let authority_ids = authorities.into_iter().map(|a| a.authority_id()).collect();
95			Ok(GetCommitteeResponse::new_legacy(epoch, authority_ids))
96		} else {
97			let (epoch, authority_data) = api.get_current_committee(block).map_err(err_debug)?;
98			Ok(GetCommitteeResponse::new(epoch, authority_data))
99		}
100	}
101
102	fn get_next_committee_versioned(
103		&self,
104		block: Block::Hash,
105	) -> QueryResult<Option<GetCommitteeResponse>> {
106		let api = self.client.runtime_api();
107
108		if self.validator_management_api_version(block)? < 2 {
109			#[allow(deprecated)]
110			Ok(api.get_next_committee_before_version_2(block).map_err(err_debug)?.map(
111				|(epoch, authorities)| {
112					let authority_ids = authorities.iter().map(|a| a.authority_id()).collect();
113					GetCommitteeResponse::new_legacy(epoch, authority_ids)
114				},
115			))
116		} else {
117			Ok(api
118				.get_next_committee(block)
119				.map_err(err_debug)?
120				.map(|(epoch, authority_data)| GetCommitteeResponse::new(epoch, authority_data)))
121		}
122	}
123}
124
125#[async_trait]
126#[allow(deprecated)]
127impl<C, Block, AuthorityId, AuthorityKeys> SessionValidatorManagementQueryApi
128	for SessionValidatorManagementQuery<C, Block, AuthorityId, AuthorityKeys>
129where
130	Block: BlockT,
131	NumberFor<Block>: From<u32> + Into<u32>,
132	AuthorityKeys: Decode + Encode + Send + Sync + 'static,
133	AuthorityId: AsRef<[u8]> + Decode + Encode + Send + Sync + 'static + Clone,
134	C: Send + Sync + 'static,
135	C: ProvideRuntimeApi<Block>,
136	C: HeaderBackend<Block>,
137	C::Api: sp_api::Core<Block>,
138	C::Api: GetSidechainStatus<Block>,
139	C::Api: SessionValidatorManagementApi<Block, AuthorityId, AuthorityKeys, ScEpochNumber>,
140	C::Api: GetGenesisUtxo<Block>,
141	C::Api: CandidateValidationApi<Block>,
142{
143	fn get_epoch_committee(&self, epoch_number: u64) -> QueryResult<GetCommitteeResponse> {
144		let epoch_number = ScEpochNumber(epoch_number);
145		let Info { genesis_hash, best_number: latest_block, best_hash, .. } = self.client.info();
146
147		if epoch_number.is_zero() {
148			let genesis_committee = self.get_current_committee_versioned(genesis_hash)?;
149			return Ok(GetCommitteeResponse { sidechain_epoch: 0, ..genesis_committee });
150		}
151
152		let first_epoch = {
153			let second_block = (self.client)
154				.hash(1.into())
155				.map_err(|err| {
156					format!("Node is not in archive mode, not able to fetch first block: {err:?}")
157				})?
158				.ok_or("Only the Genesis Block exists at the moment!")?;
159			(self.client.runtime_api())
160				.get_sidechain_status(second_block)
161				.map_err(err_debug)?
162				.epoch
163		};
164
165		if epoch_number < first_epoch {
166			return Err(format!("Epoch {} is earlier than the Initial Epoch!", epoch_number));
167		}
168
169		let epoch_of_latest_block =
170			self.client.get_epoch_of_block(latest_block).map_err(err_debug)?;
171
172		if epoch_number > epoch_of_latest_block.next() {
173			return Err(format!("Committee is unknown for epoch {epoch_number}"));
174		}
175
176		if epoch_number == epoch_of_latest_block.next() {
177			self.get_next_committee_versioned(best_hash)?
178				.ok_or(format!("Committee is unknown for the next epoch: {epoch_number}"))
179		} else {
180			let block_hash = self
181				.client
182				.find_block(AnyBlockInEpoch { epoch: epoch_number })
183				.map_err(err_debug)?;
184			self.get_current_committee_versioned(block_hash)
185		}
186	}
187
188	async fn get_registrations(
189		&self,
190		mc_epoch_number: McEpochNumber,
191		mc_public_key: StakePoolPublicKey,
192	) -> QueryResult<Vec<CandidateRegistrationEntry>> {
193		let api = self.client.runtime_api();
194		let best_block = self.client.info().best_hash;
195		let scripts = api.get_main_chain_scripts(best_block).map_err(err_debug)?;
196		let mut registrations_map = self
197			.candidates_registrations_for_epoch(
198				mc_epoch_number,
199				scripts.committee_candidate_address,
200			)
201			.await?;
202		Ok(registrations_map.remove(&to_hex(&mc_public_key.0, false)).unwrap_or(vec![]))
203	}
204
205	async fn get_ariadne_parameters(
206		&self,
207		epoch_number: McEpochNumber,
208	) -> QueryResult<AriadneParameters> {
209		let api = self.client.runtime_api();
210		let best_block = self.client.info().best_hash;
211		let scripts = api.get_main_chain_scripts(best_block).map_err(err_debug)?;
212		let ariadne_parameters_response = self
213			.candidate_data_source
214			.get_ariadne_parameters(
215				epoch_number,
216				scripts.d_parameter_policy_id,
217				scripts.permissioned_candidates_policy_id,
218			)
219			.await
220			.map_err(err_debug)?;
221
222		let candidate_registrations = self
223			.candidates_registrations_for_epoch(epoch_number, scripts.committee_candidate_address)
224			.await?;
225		let validate_permissioned_candidate =
226			|candidate: &sidechain_domain::PermissionedCandidateData| {
227				api.validate_permissioned_candidate_data(best_block, candidate.clone())
228			};
229
230		let permissioned_candidates = match ariadne_parameters_response.permissioned_candidates {
231			None => None,
232			Some(permissioned_candidates) => Some(
233				permissioned_candidates
234					.into_iter()
235					.map(|candidate| {
236						let validation_result =
237							validate_permissioned_candidate(&candidate).map_err(err_debug)?;
238						Ok::<PermissionedCandidateData, String>(PermissionedCandidateData::new(
239							candidate,
240							validation_result,
241						))
242					})
243					.collect::<Result<Vec<_>, _>>()?,
244			),
245		};
246
247		Ok(AriadneParameters {
248			d_parameter: ariadne_parameters_response.d_parameter.into(),
249			permissioned_candidates,
250			candidate_registrations,
251		})
252	}
253}
254
255fn err_debug<T: std::fmt::Debug>(err: T) -> String {
256	format!("{err:?}")
257}