partner_chains_mock_data_sources/
candidate.rs

1use crate::Result;
2use async_trait::async_trait;
3use authority_selection_inherents::*;
4use hex_literal::hex;
5use log::{debug, info};
6use serde::*;
7use sidechain_domain::byte_string::*;
8use sidechain_domain::*;
9
10#[derive(Deserialize, Debug, Clone)]
11pub struct MockRegistration {
12	pub name: Option<String>,
13	pub sidechain_pub_key: ByteString,
14	pub mainchain_pub_key: ByteString,
15	pub mainchain_signature: ByteString,
16	pub sidechain_signature: ByteString,
17	pub registration_utxo: UtxoId,
18	pub status: MockRegistrationStatus,
19	pub aura_pub_key: ByteString,
20	pub grandpa_pub_key: ByteString,
21}
22
23impl MockRegistration {
24	/// Returns an info string like: "Bob(0x039...1f27, active)"
25	pub fn info_string(&self) -> String {
26		let name = self.name.clone().unwrap_or("<Unnamed>".into());
27		let status = match self.status {
28			MockRegistrationStatus::Active => "active".to_string(),
29			MockRegistrationStatus::PendingActivation { effective_at } => {
30				format!("active at {effective_at}")
31			},
32			MockRegistrationStatus::PendingDeregistration { effective_at } => {
33				format!("active until {effective_at}")
34			},
35		};
36		let mut short_addr = self.sidechain_pub_key.to_hex_string();
37		short_addr.replace_range(5..(short_addr.len() - 4), "...");
38		format!("{name}({short_addr}, {status})")
39	}
40}
41
42impl From<MockRegistration> for CandidateRegistrations {
43	fn from(mock: MockRegistration) -> Self {
44		let stake_pool_public_key = StakePoolPublicKey(mock.mainchain_pub_key.0.try_into().expect(
45			"Invalid mock configuration. 'mainchain_pub_key' public key should be 32 bytes.",
46		));
47		let registrations = vec![RegistrationData {
48			registration_utxo: mock.registration_utxo,
49			sidechain_signature: SidechainSignature(mock.sidechain_signature.0.clone()),
50			mainchain_signature: MainchainSignature(
51				mock.mainchain_signature.0.try_into().expect("Mainchain signature is 64 bytes"),
52			),
53			cross_chain_signature: CrossChainSignature(mock.sidechain_signature.0.clone()),
54			sidechain_pub_key: SidechainPublicKey(mock.sidechain_pub_key.0.clone()),
55			cross_chain_pub_key: CrossChainPublicKey(mock.sidechain_pub_key.0.clone()),
56			utxo_info: UtxoInfo {
57				utxo_id: UtxoId {
58					tx_hash: McTxHash(hex!(
59						"5a9b57731df0e008c5aa7296482c033212b71a3c1796ff00c10db7150c1f3d1d"
60					)),
61					index: UtxoIndex(9),
62				},
63				epoch_number: McEpochNumber(123),
64				block_number: McBlockNumber(12345),
65				slot_number: McSlotNumber(123456),
66				tx_index_within_block: McTxIndexInBlock(12),
67			},
68			tx_inputs: vec![mock.registration_utxo],
69			keys: CandidateKeys(vec![
70				AuraPublicKey(mock.aura_pub_key.0).into(),
71				GrandpaPublicKey(mock.grandpa_pub_key.0).into(),
72			]),
73		}];
74		let stake_delegation = Some(StakeDelegation(333));
75		CandidateRegistrations { stake_pool_public_key, registrations, stake_delegation }
76	}
77}
78
79#[derive(Deserialize, Debug, Clone)]
80pub enum MockRegistrationStatus {
81	Active,
82	PendingActivation { effective_at: u64 },
83	PendingDeregistration { effective_at: u64 },
84}
85
86#[derive(Deserialize, Debug, Clone)]
87pub struct MockPermissionedCandidate {
88	name: Option<String>,
89	sidechain_pub_key: ByteString,
90	aura_pub_key: ByteString,
91	grandpa_pub_key: ByteString,
92}
93
94impl MockPermissionedCandidate {
95	/// Returns an info string like: Bob(0x039...1f27)
96	pub fn info_string(&self) -> String {
97		let name = self.clone().name.unwrap_or("<unnamed>".into());
98		let mut short_addr = self.sidechain_pub_key.to_hex_string();
99		short_addr.replace_range(5..(short_addr.len() - 4), "...");
100		format!("{}({})", name, short_addr)
101	}
102}
103
104impl From<MockPermissionedCandidate> for PermissionedCandidateData {
105	fn from(
106		MockPermissionedCandidate {
107			name: _,
108			sidechain_pub_key,
109			aura_pub_key,
110			grandpa_pub_key,
111		}: MockPermissionedCandidate,
112	) -> Self {
113		Self {
114			sidechain_public_key: SidechainPublicKey(sidechain_pub_key.0),
115			keys: CandidateKeys(vec![
116				AuraPublicKey(aura_pub_key.0).into(),
117				GrandpaPublicKey(grandpa_pub_key.0).into(),
118			]),
119		}
120	}
121}
122
123#[derive(Deserialize, Clone, Debug)]
124pub struct MockDParam {
125	permissioned: u16,
126	registered: u16,
127}
128
129impl MockDParam {
130	pub fn info_string(&self) -> String {
131		format!("permissioned: {}, registered: {}", self.permissioned, self.registered)
132	}
133}
134
135impl From<MockDParam> for DParameter {
136	fn from(MockDParam { permissioned, registered }: MockDParam) -> Self {
137		Self { num_permissioned_candidates: permissioned, num_registered_candidates: registered }
138	}
139}
140
141/// Mock authority selection data for a single epoch
142#[derive(Deserialize, Clone, Debug)]
143pub struct MockEpochCandidates {
144	/// Permissioned candidates
145	pub permissioned: Vec<MockPermissionedCandidate>,
146	/// Active registrations (including invalid ones)
147	pub registrations: Vec<MockRegistration>,
148	/// Epoch nonce
149	pub nonce: ByteString,
150	/// Ariadne D-Parameter
151	pub d_parameter: MockDParam,
152}
153
154/// Configuration of the mocked authority selection data source
155pub struct MockRegistrationsConfig {
156	/// List of epoch configurations
157	/// These are returned for each epoch in a round-robin fashion
158	pub epoch_rotation: Vec<MockEpochCandidates>,
159}
160
161impl MockRegistrationsConfig {
162	/// Reads the mocked authority selection data from the file indicated by the
163	/// `MOCK_REGISTRATIONS_FILE` environment variable.
164	pub fn read() -> Result<MockRegistrationsConfig> {
165		let registrations_file_path = std::env::var("MOCK_REGISTRATIONS_FILE")?;
166		let registrations_config = Self::read_registrations(&registrations_file_path)?;
167		Ok(registrations_config)
168	}
169
170	/// Reads the mocked authority selection data from file.
171	pub fn read_registrations(path: &str) -> Result<MockRegistrationsConfig> {
172		info!("Reading registrations from: {path}");
173		let file = std::fs::File::open(path)?;
174		let epoch_rotation: Vec<MockEpochCandidates> = serde_json::from_reader(file)?;
175		info!("Loaded {} registration rotations", epoch_rotation.len());
176		Ok(MockRegistrationsConfig { epoch_rotation })
177	}
178}
179
180/// Mock authority selection data source that serves registration data in a round-robin fashion
181///
182/// # Creating the data source
183///
184/// This data source can be created by wrapping a manually created [MockRegistrationsConfig].
185/// However, the preferred way to do it is by loading the registrations data from a JSON file
186/// using the [MockRegistrationsConfig::read_registrations] method.
187///
188/// An example configuration file can look like this:
189/// ```json
190#[doc = include_str!("../examples/registrations.json")]
191/// ```
192///
193/// See the structure and documentation of [MockEpochCandidates] for more information.
194///
195/// This file can be loaded and used to create a data source like this:
196///
197/// ```rust
198/// # use std::io::Write;
199/// # use std::fs::File;
200/// # write!(File::create("registrations.json").unwrap(), "{}", include_str!("../examples/registrations.json"));
201///
202/// use partner_chains_mock_data_sources::*;
203///
204/// let registrations_data = MockRegistrationsConfig::read_registrations("registrations.json").unwrap();
205///
206/// let data_source = AuthoritySelectionDataSourceMock { registrations_data };
207/// ```
208///
209/// Alternatively the data source can be created using `AuthoritySelectionDataSourceMock::new_from_env`:
210///
211/// ```rust,no_run
212/// use partner_chains_mock_data_sources::AuthoritySelectionDataSourceMock;
213///
214/// let data_source = AuthoritySelectionDataSourceMock::new_from_env();
215/// ```
216///
217/// The file name fill be then sourced from the `MOCK_REGISTRATIONS_FILE` environment variable.
218///
219pub struct AuthoritySelectionDataSourceMock {
220	/// Data source configuration containing the mock data to be served
221	pub registrations_data: MockRegistrationsConfig,
222}
223
224impl AuthoritySelectionDataSourceMock {
225	pub(crate) fn epoch_data(&self, epoch_number: u32) -> MockEpochCandidates {
226		let rotation_no: usize =
227			epoch_number as usize % (self.registrations_data.epoch_rotation.len());
228		self.registrations_data.epoch_rotation[rotation_no].clone()
229	}
230
231	/// Creates a new mocked authority selection data source using configuration from th
232	/// file pointed to by the `MOCK_REGISTRATIONS_FILE` environment variable.
233	pub fn new_from_env() -> Result<Self> {
234		let registrations_data = MockRegistrationsConfig::read()?;
235		Ok(AuthoritySelectionDataSourceMock { registrations_data })
236	}
237}
238
239#[async_trait]
240impl AuthoritySelectionDataSource for AuthoritySelectionDataSourceMock {
241	async fn get_ariadne_parameters(
242		&self,
243		epoch_number: McEpochNumber,
244		_d_parameter_validator: PolicyId,
245		_permissioned_candidates_validator: PolicyId,
246	) -> Result<AriadneParameters> {
247		let epoch_number = epoch_number.0;
248		debug!("Received get_d_parameter_for_epoch({epoch_number}) request");
249
250		let d_parameter = self.epoch_data(epoch_number).d_parameter;
251		debug!("    Responding with: {}", d_parameter.info_string());
252
253		debug!("Received get_permissioned_candidates_for_epoch({epoch_number}) request");
254
255		let candidates = self.epoch_data(epoch_number).permissioned;
256
257		debug!(
258			"    Responding with: {:?}",
259			candidates.iter().cloned().map(|c| c.info_string()).collect::<Vec<_>>()
260		);
261
262		let permissioned_candidates: Option<Vec<PermissionedCandidateData>> =
263			Some(candidates.into_iter().map(|p| p.into()).collect());
264
265		Ok(AriadneParameters { d_parameter: d_parameter.into(), permissioned_candidates })
266	}
267
268	async fn get_candidates(
269		&self,
270		epoch: McEpochNumber,
271		_committee_candidate_address: MainchainAddress,
272	) -> Result<Vec<CandidateRegistrations>> {
273		let epoch_number = epoch.0;
274		debug!("Received get_candidates({epoch_number}) request");
275
276		let epoch_conf = self.epoch_data(epoch_number);
277		let registrations = epoch_conf.registrations;
278
279		debug!(
280			"    Responding with:
281    Registrations: {:?}",
282			registrations.iter().cloned().map(|r| r.info_string()).collect::<Vec<_>>()
283		);
284		Ok(registrations.into_iter().map(CandidateRegistrations::from).collect())
285	}
286
287	async fn get_epoch_nonce(&self, epoch_number: McEpochNumber) -> Result<Option<EpochNonce>> {
288		let epoch_number = epoch_number.0;
289		debug!("Received get_epoch_nonce({epoch_number}) request");
290		let epoch_conf = self.epoch_data(epoch_number);
291		debug!(
292			"    Responding with:
293    Nonce: {}",
294			epoch_conf.nonce.to_hex_string(),
295		);
296		Ok(Some(EpochNonce(epoch_conf.nonce.clone().0)))
297	}
298
299	async fn data_epoch(&self, for_epoch: McEpochNumber) -> Result<McEpochNumber> {
300		Ok(McEpochNumber(for_epoch.0 - 2))
301	}
302}