authority_selection_inherents/
ariadne_inherent_data_provider.rs

1//! Logic to provide committee selection inputs compatible with the Ariadne algorithm, based on Cardano observation.
2#[cfg(feature = "std")]
3use crate::authority_selection_inputs::AuthoritySelectionDataSource;
4use crate::authority_selection_inputs::AuthoritySelectionInputs;
5use parity_scale_codec::{Decode, Encode};
6#[cfg(feature = "std")]
7use {
8	crate::authority_selection_inputs::AuthoritySelectionInputsCreationError,
9	sidechain_domain::mainchain_epoch::{MainchainEpochConfig, MainchainEpochDerivation},
10	sidechain_domain::*,
11	sidechain_slots::ScSlotConfig,
12	sp_api::ProvideRuntimeApi,
13	sp_consensus_slots::Slot,
14	sp_inherents::{InherentData, InherentIdentifier},
15	sp_runtime::traits::Block as BlockT,
16	sp_session_validator_management::CommitteeMember as CommitteeMemberT,
17	sp_session_validator_management::{
18		INHERENT_IDENTIFIER, InherentError, MainChainScripts, SessionValidatorManagementApi,
19	},
20};
21
22/// Inherent data type provided by [AriadneInherentDataProvider].
23pub type InherentType = AuthoritySelectionInputs;
24
25#[derive(Clone, Debug, Encode, Decode)]
26/// Inherent data provider providing inputs for authority selection.
27pub struct AriadneInherentDataProvider {
28	/// Authority selection inputs.
29	pub data: Option<AuthoritySelectionInputs>,
30}
31
32#[cfg(feature = "std")]
33impl AriadneInherentDataProvider {
34	/// Creates a new [AriadneInherentDataProvider] for use in [sp_inherents::CreateInherentDataProviders].
35	///
36	/// Parameters:
37	/// - `client`: runtime client capable of providing [SessionValidatorManagementApi] runtime API
38	/// - `sc_slot_config`: partner chain slot configuration
39	/// - `mc_epoch_config`: main chain epoch configuration
40	/// - `parent_hash`: parent hash of the current block
41	/// - `slot`: slot of the current block
42	/// - `data_source`: data source implementing [AuthoritySelectionDataSource]
43	/// - `mc_reference_epoch`: latest stable mainchain epoch
44	pub async fn new<Block, CommitteeMember, T>(
45		client: &T,
46		sc_slot_config: &ScSlotConfig,
47		mc_epoch_config: &MainchainEpochConfig,
48		parent_hash: <Block as BlockT>::Hash,
49		slot: Slot,
50		data_source: &(dyn AuthoritySelectionDataSource + Send + Sync),
51		mc_reference_epoch: McEpochNumber,
52	) -> Result<Self, InherentProviderCreationError>
53	where
54		CommitteeMember: CommitteeMemberT + Decode + Encode,
55		CommitteeMember::AuthorityKeys: Decode + Encode,
56		CommitteeMember::AuthorityId: Decode + Encode,
57		Block: BlockT,
58		T: ProvideRuntimeApi<Block> + Send + Sync,
59		T::Api: SessionValidatorManagementApi<
60				Block,
61				CommitteeMember,
62				AuthoritySelectionInputs,
63				ScEpochNumber,
64			>,
65	{
66		let for_mc_epoch = mc_epoch_for_next_ariadne_cidp(
67			client,
68			sc_slot_config,
69			mc_epoch_config,
70			parent_hash,
71			slot,
72		)?;
73
74		let data_epoch = data_source.data_epoch(for_mc_epoch).await?;
75		// We could accept mc_reference at last slot of data_epoch, but calculations are much easier like that.
76		// Additionally, in current implementation, the inequality below is always true, thus there is no need to make it more accurate.
77		let scripts = client.runtime_api().get_main_chain_scripts(parent_hash)?;
78		if data_epoch < mc_reference_epoch {
79			Ok(AriadneInherentDataProvider::from_mc_data(data_source, for_mc_epoch, scripts)
80				.await?)
81		} else {
82			Ok(AriadneInherentDataProvider { data: None })
83		}
84	}
85
86	async fn from_mc_data(
87		candidate_data_source: &(dyn AuthoritySelectionDataSource + Send + Sync),
88		for_epoch: McEpochNumber,
89		scripts: MainChainScripts,
90	) -> Result<Self, InherentProviderCreationError> {
91		Ok(Self {
92			data: Some(
93				AuthoritySelectionInputs::from_mc_data(candidate_data_source, for_epoch, scripts)
94					.await?,
95			),
96		})
97	}
98}
99
100#[cfg(feature = "std")]
101#[derive(Debug, thiserror::Error)]
102/// Error type returned when creation of [AriadneInherentDataProvider] fails.
103pub enum InherentProviderCreationError {
104	/// Slot represents a timestamp bigger than of [u64::MAX].
105	#[error("Slot represents a timestamp bigger than of u64::MAX")]
106	SlotTooBig,
107	/// Couldn't convert timestamp to main chain epoch.
108	#[error("Couldn't convert timestamp to main chain epoch: {0}")]
109	McEpochDerivationError(#[from] sidechain_domain::mainchain_epoch::EpochDerivationError),
110	/// Runtime API call failed.
111	#[error("Runtime API call failed: {0}")]
112	ApiError(#[from] sp_api::ApiError),
113	/// Failed to create authority selection inputs.
114	#[error("Failed to create authority selection inputs: {0}")]
115	InputsCreationError(#[from] AuthoritySelectionInputsCreationError),
116	/// Data source call failed.
117	#[error("Data source call failed: {0}")]
118	DataSourceError(#[from] Box<dyn std::error::Error + Send + Sync>),
119}
120
121#[cfg(feature = "std")]
122fn mc_epoch_for_next_ariadne_cidp<Block, CommitteeMember, T>(
123	client: &T,
124	sc_slot_config: &ScSlotConfig,
125	epoch_config: &MainchainEpochConfig,
126	parent_hash: <Block as BlockT>::Hash,
127	slot: Slot,
128) -> Result<McEpochNumber, InherentProviderCreationError>
129where
130	Block: BlockT,
131	CommitteeMember: Decode + Encode + CommitteeMemberT,
132	CommitteeMember::AuthorityKeys: Decode + Encode,
133	CommitteeMember::AuthorityId: Decode + Encode,
134	T: ProvideRuntimeApi<Block> + Send + Sync,
135	T::Api: SessionValidatorManagementApi<
136			Block,
137			CommitteeMember,
138			AuthoritySelectionInputs,
139			ScEpochNumber,
140		>,
141{
142	let next_unset_epoch = client.runtime_api().get_next_unset_epoch_number(parent_hash)?;
143
144	let for_sc_epoch_number = {
145		// A special case for the genesis committee (current epoch 0, next unset epoch 1).
146		// The genesis committee epoch is initialized with 0, so in the very first block we need to provide
147		// the epoch number based on the current slot number
148		if next_unset_epoch == ScEpochNumber(1) {
149			sc_slot_config.epoch_number(slot)
150		} else {
151			next_unset_epoch
152		}
153	};
154
155	sc_epoch_to_mc_epoch(for_sc_epoch_number, sc_slot_config, epoch_config)
156}
157
158#[cfg(feature = "std")]
159fn sc_epoch_to_mc_epoch(
160	sc_epoch: ScEpochNumber,
161	sc_slot_config: &ScSlotConfig,
162	epoch_config: &MainchainEpochConfig,
163) -> Result<McEpochNumber, InherentProviderCreationError> {
164	let timestamp = sc_slot_config
165		.epoch_start_time(sc_epoch)
166		.ok_or(InherentProviderCreationError::SlotTooBig)?;
167
168	epoch_config
169		.timestamp_to_mainchain_epoch(timestamp)
170		.map_err(InherentProviderCreationError::McEpochDerivationError)
171}
172
173#[cfg(feature = "std")]
174#[async_trait::async_trait]
175impl sp_inherents::InherentDataProvider for AriadneInherentDataProvider {
176	async fn provide_inherent_data(
177		&self,
178		inherent_data: &mut InherentData,
179	) -> Result<(), sp_inherents::Error> {
180		match &self.data {
181			None => Ok(()),
182			Some(data) => inherent_data.put_data(INHERENT_IDENTIFIER, data),
183		}
184	}
185
186	async fn try_handle_error(
187		&self,
188		identifier: &InherentIdentifier,
189		error: &[u8],
190	) -> Option<Result<(), sp_inherents::Error>> {
191		// Don't process inherents from other modules
192		if *identifier != INHERENT_IDENTIFIER {
193			return None;
194		}
195
196		let mut error = error;
197		Some(Err(sp_inherents::Error::Application(Box::from(
198			<InherentError as parity_scale_codec::Decode>::decode(&mut error).ok()?,
199		))))
200	}
201}
202
203#[cfg(test)]
204mod tests {
205	use super::*;
206	use crate::ariadne_inherent_data_provider::AriadneInherentDataProvider;
207	use crate::mock::MockAuthoritySelectionDataSource;
208	use crate::runtime_api_mock::*;
209	use SlotDuration;
210	use sidechain_domain::mainchain_epoch::*;
211	use sidechain_slots::*;
212	use sp_core::H256;
213	use sp_core::offchain::Timestamp;
214
215	#[tokio::test]
216	async fn return_empty_ariadne_cidp_if_runtime_requests_too_new_epoch() {
217		// This is the epoch number that is too new
218		let next_unset_epoch_number = ScEpochNumber(42);
219		let mc_reference_epoch = McEpochNumber(1);
220		let empty_ariadne_idp = AriadneInherentDataProvider::new(
221			&client(next_unset_epoch_number),
222			&sc_slot_config(),
223			&epoch_config(),
224			H256::zero(),
225			// This is the slot that will be used to calculate current_epoch_number
226			Slot::from(400u64),
227			&MockAuthoritySelectionDataSource::default(),
228			mc_reference_epoch,
229		)
230		.await;
231
232		assert!(empty_ariadne_idp.is_ok());
233		assert!(empty_ariadne_idp.unwrap().data.is_none());
234	}
235
236	#[tokio::test]
237	async fn error_if_num_permissioned_candidates_non_zero_and_no_permissioned_candidate_list() {
238		use crate::ariadne_inherent_data_provider::InherentProviderCreationError::InputsCreationError;
239		use crate::authority_selection_inputs::AuthoritySelectionInputsCreationError::AriadneParametersQuery;
240
241		let next_unset_epoch_number = ScEpochNumber(42);
242		let mc_reference_epoch = McEpochNumber(5);
243		let ariadne_idp = AriadneInherentDataProvider::new(
244			&client(next_unset_epoch_number),
245			&sc_slot_config(),
246			&epoch_config(),
247			H256::zero(),
248			// This is the slot that will be used to calculate current_epoch_number
249			Slot::from(400u64),
250			&MockAuthoritySelectionDataSource::default()
251				.with_permissioned_candidates(vec![None, None, None, None, None])
252				.with_num_permissioned_candidates(3),
253			mc_reference_epoch,
254		)
255		.await;
256
257		assert!(ariadne_idp.is_err());
258		assert!(matches!(
259			ariadne_idp.unwrap_err(),
260			InputsCreationError(AriadneParametersQuery(_, _, _, _))
261		));
262	}
263
264	#[tokio::test]
265	async fn ok_if_num_permissioned_candidates_zero_and_no_permissioned_candidate_list() {
266		let next_unset_epoch_number = ScEpochNumber(42);
267		let mc_reference_epoch = McEpochNumber(5);
268		let ariadne_idp = AriadneInherentDataProvider::new(
269			&client(next_unset_epoch_number),
270			&sc_slot_config(),
271			&epoch_config(),
272			H256::zero(),
273			// This is the slot that will be used to calculate current_epoch_number
274			Slot::from(400u64),
275			&MockAuthoritySelectionDataSource::default()
276				.with_permissioned_candidates(vec![None, None, None, None, None])
277				.with_num_permissioned_candidates(0),
278			mc_reference_epoch,
279		)
280		.await;
281
282		assert!(ariadne_idp.is_ok());
283		assert!(ariadne_idp.unwrap().data.is_some());
284	}
285
286	fn sc_slot_config() -> ScSlotConfig {
287		ScSlotConfig {
288			slots_per_epoch: SlotsPerEpoch(10),
289			slot_duration: SlotDuration::from_millis(1000),
290		}
291	}
292
293	fn client(next_unset_epoch_number: ScEpochNumber) -> TestApi {
294		TestApi { next_unset_epoch_number }
295	}
296
297	fn epoch_config() -> MainchainEpochConfig {
298		MainchainEpochConfig {
299			first_epoch_timestamp_millis: Timestamp::from_unix_millis(0),
300			first_epoch_number: 0,
301			epoch_duration_millis: Duration::from_millis(
302				u64::from(sc_slot_config().slots_per_epoch.0)
303					* sc_slot_config().slot_duration.as_millis()
304					* 10,
305			),
306			first_slot_number: 0,
307			slot_duration_millis: Duration::from_millis(1000),
308		}
309	}
310}