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