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