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