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