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