partner_chains_plutus_data/
permissioned_candidates.rs

1//! Plutus data types for permissioned candidates.
2use cardano_serialization_lib::{BigNum, PlutusData, PlutusList};
3use sidechain_domain::*;
4
5use crate::{
6	DataDecodingError, DecodingResult, VersionedDatum, VersionedDatumWithLegacy,
7	VersionedGenericDatum, candidate_keys::*,
8};
9
10#[derive(Clone, Debug, PartialEq)]
11/// Datum representing a list of permissioned candidates.
12pub enum PermissionedCandidateDatums {
13	/// Initial/legacy datum schema. If a datum doesn't contain a version, it is assumed to be V0
14	V0(Vec<PermissionedCandidateDatumV0>),
15	/// Schema with generic set of keys
16	V1(Vec<PermissionedCandidateDatumV1>),
17}
18
19#[derive(Clone, Debug, PartialEq)]
20/// Datum representing a permissioned candidate.
21pub struct PermissionedCandidateDatumV0 {
22	/// Sidechain public key of the trustless candidate
23	pub sidechain_public_key: SidechainPublicKey,
24	/// Aura public key of the trustless candidate
25	pub aura_public_key: AuraPublicKey,
26	/// GRANDPA public key of the trustless candidate
27	pub grandpa_public_key: GrandpaPublicKey,
28}
29
30impl From<PermissionedCandidateDatumV1> for PermissionedCandidateData {
31	// In the follow-up PermissionedCandidateData will become parametrized
32	// with T: OpaqueKeys, this function will be re-implemented.
33	fn from(value: PermissionedCandidateDatumV1) -> Self {
34		let PermissionedCandidateDatumV1 { partner_chains_key, keys } = value;
35		PermissionedCandidateData { sidechain_public_key: partner_chains_key, keys }
36	}
37}
38
39#[derive(Clone, Debug, PartialEq)]
40/// Datum representing a permissioned candidate with arbitrary set of keys
41pub struct PermissionedCandidateDatumV1 {
42	/// Partner Chains key identifier and bytes
43	pub partner_chains_key: SidechainPublicKey,
44	/// Represents arbitrary set of keys with 4 character identifier
45	pub keys: CandidateKeys,
46}
47
48impl TryFrom<PlutusData> for PermissionedCandidateDatums {
49	type Error = DataDecodingError;
50	fn try_from(datum: PlutusData) -> DecodingResult<Self> {
51		Self::decode(&datum)
52	}
53}
54
55impl From<PermissionedCandidateDatumV0> for PermissionedCandidateData {
56	fn from(value: PermissionedCandidateDatumV0) -> Self {
57		Self {
58			sidechain_public_key: value.sidechain_public_key,
59			keys: CandidateKeys(vec![
60				value.aura_public_key.into(),
61				value.grandpa_public_key.into(),
62			]),
63		}
64	}
65}
66
67impl From<PermissionedCandidateDatums> for Vec<PermissionedCandidateData> {
68	fn from(value: PermissionedCandidateDatums) -> Self {
69		match value {
70			PermissionedCandidateDatums::V0(v) => v.into_iter().map(|d| d.into()).collect(),
71			PermissionedCandidateDatums::V1(v) => v.into_iter().map(|d| d.into()).collect(),
72		}
73	}
74}
75
76/// Converts a list of [PermissionedCandidateData] values to [VersionedGenericDatum] encoded as [PlutusData].
77/// Version 0 is used for specific set of Partner Chains Key: partner chains key, AURA, Grandpa
78/// If other set of key is used, then version 1 is used.
79/// Encoding:
80/// ```ignore
81///   VersionedGenericDatum:
82///   - datum: Constr 0 []
83///   - appendix:
84///     [
85///       [ candidates[0].sidechain_public_key
86///       , candidates[0].aura_public_key
87///       , candidates[0].grandpa_public_key
88///       ]
89///     ,
90///       [ candidates[1].sidechain_public_key
91///       , candidates[1].aura_public_key
92///       , candidates[1].grandpa_public_key
93///       ]
94///       // etc.
95///     ]
96///   - version: 0
97/// or:
98///   VersionedGenericDatum:
99///   - datum: Constr 0 []
100///   - appendix:
101///     [
102///       [ candidates[0].sidechain_public_key
103/// 	  ,
104///         [
105/// 	      [ candidates[0].keys[0].id,
106///           , candidates[0].keys[0].bytes
107///           ]
108///         , [ candidates[0].keys[1].id,
109///           , candidates[0].keys[1].bytes
110///           ]
111///           // etc.
112///         ]
113///       ]
114///     ,
115///       [ candidates[1].sidechain_public_key
116/// 	  ,
117///         [
118/// 	      [ candidates[1].keys[0].id,
119///           , candidates[1].keys[0].bytes
120///           ]
121///         , [ candidates[1].keys[1].id,
122///           , candidates[1].keys[1].bytes
123///           ]
124///           // etc.
125///         ]
126///       ]
127///       // etc.
128///     ]
129///   - version: 1
130/// ```
131
132pub fn permissioned_candidates_to_plutus_data(
133	candidates: &[PermissionedCandidateData],
134) -> PlutusData {
135	fn candidates_to_plutus_data_v0(candidates: &[PermissionedCandidateData]) -> PlutusData {
136		let mut list = PlutusList::new();
137		for candidate in candidates {
138			let mut candidate_datum = PlutusList::new();
139			candidate_datum.add(&PlutusData::new_bytes(candidate.sidechain_public_key.0.clone()));
140			for key in candidate.keys.0.iter() {
141				candidate_datum.add(&PlutusData::new_bytes(key.bytes.clone()));
142			}
143			list.add(&PlutusData::new_list(&candidate_datum));
144		}
145		let appendix = PlutusData::new_list(&list);
146		VersionedGenericDatum {
147			datum: PlutusData::new_empty_constr_plutus_data(&BigNum::zero()),
148			appendix,
149			version: 0,
150		}
151		.into()
152	}
153
154	fn candidates_to_plutus_data_v1(candidates: &[PermissionedCandidateData]) -> PlutusData {
155		let mut list = PlutusList::new();
156		for candidate in candidates {
157			let mut candidate_datum = PlutusList::new();
158			candidate_datum.add(&PlutusData::new_bytes(candidate.sidechain_public_key.0.clone()));
159			candidate_datum.add(&candidate_keys_to_plutus(&candidate.keys));
160			list.add(&PlutusData::new_list(&candidate_datum));
161		}
162		VersionedGenericDatum {
163			datum: PlutusData::new_empty_constr_plutus_data(&BigNum::zero()),
164			appendix: PlutusData::new_list(&list),
165			version: 1,
166		}
167		.into()
168	}
169
170	if candidates.iter().all(|c| c.keys.has_only_aura_and_grandpa_keys()) {
171		candidates_to_plutus_data_v0(candidates)
172	} else {
173		candidates_to_plutus_data_v1(candidates)
174	}
175}
176
177impl PermissionedCandidateDatums {
178	/// Parses plutus data schema in accordance with V1 schema
179	fn decode_v1_appendix(data: &PlutusData) -> Result<Self, String> {
180		let permissioned_candidates = data
181			.as_list()
182			.and_then(|list_datums| {
183				list_datums
184					.into_iter()
185					.map(decode_v1_candidate_datum)
186					.collect::<Option<Vec<PermissionedCandidateDatumV1>>>()
187			})
188			.ok_or("Expected [[ByteString, ByteString], [[ByteString, ByteString], ... ]]")?;
189		Ok(Self::V1(permissioned_candidates))
190	}
191}
192
193impl VersionedDatumWithLegacy for PermissionedCandidateDatums {
194	const NAME: &str = "PermissionedCandidateDatums";
195
196	/// Parses plutus data schema that was used before datum versioning was added. Kept for backwards compatibility.
197	fn decode_legacy(data: &PlutusData) -> Result<Self, String> {
198		let permissioned_candidates = data
199			.as_list()
200			.and_then(|list_datums| {
201				list_datums
202					.into_iter()
203					.map(decode_legacy_candidate_datum)
204					.collect::<Option<Vec<PermissionedCandidateDatumV0>>>()
205			})
206			.ok_or("Expected [[ByteString, ByteString, ByteString]]")?;
207
208		Ok(Self::V0(permissioned_candidates))
209	}
210
211	fn decode_versioned(
212		version: u64,
213		_datum: &PlutusData,
214		appendix: &PlutusData,
215	) -> Result<Self, String> {
216		match version {
217			// v0 appendix is the same as legacy format of whole plutus data
218			0 => PermissionedCandidateDatums::decode_legacy(appendix)
219				.map_err(|msg| format!("Cannot parse appendix: {msg}")),
220			1 => PermissionedCandidateDatums::decode_v1_appendix(appendix)
221				.map_err(|msg| format!("Cannot parse appendix: {msg}")),
222			_ => Err(format!("Unknown version: {version}")),
223		}
224	}
225}
226
227/// Decodes whatever looks syntactically correct, leaving validation for runtime.
228fn decode_legacy_candidate_datum(datum: &PlutusData) -> Option<PermissionedCandidateDatumV0> {
229	let datums = datum.as_list().filter(|datums| datums.len() == 3)?;
230
231	let sc = datums.get(0).as_bytes()?;
232	let aura = datums.get(1).as_bytes()?;
233	let grandpa = datums.get(2).as_bytes()?;
234
235	Some(PermissionedCandidateDatumV0 {
236		sidechain_public_key: SidechainPublicKey(sc),
237		aura_public_key: AuraPublicKey(aura),
238		grandpa_public_key: GrandpaPublicKey(grandpa),
239	})
240}
241
242/// Decodes whatever looks syntactically correct, leaving validation for runtime.
243fn decode_v1_candidate_datum(datum: &PlutusData) -> Option<PermissionedCandidateDatumV1> {
244	// The first element has Partner Chains key, second contains all other keys
245	let outer_list = datum.as_list().filter(|l| l.len() == 2)?;
246	let partner_chains_key = SidechainPublicKey(outer_list.get(0).as_bytes()?);
247	let keys = decode_candidate_keys(&outer_list.get(1))?;
248	Some(PermissionedCandidateDatumV1 { partner_chains_key, keys })
249}
250
251#[cfg(test)]
252mod tests {
253	use super::*;
254	use crate::test_helpers::*;
255	use hex_literal::hex;
256	use pretty_assertions::assert_eq;
257	use sp_core::crypto::key_types::{AURA, GRANDPA};
258
259	#[test]
260	fn valid_legacy_permissioned_candidates() {
261		let plutus_data = test_plutus_data!({"list": [
262			{"list": [
263				{"bytes": "cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"},
264				{"bytes": "bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec"},
265				{"bytes": "9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d"}
266			]},
267			{"list": [
268				{"bytes": "79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf"},
269				{"bytes": "56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19"},
270				{"bytes": "7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32"}
271			]}
272		]});
273
274		let expected_datum = PermissionedCandidateDatums::V0(vec![
275			PermissionedCandidateDatumV0 {
276				sidechain_public_key: SidechainPublicKey(
277					hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854").into(),
278				),
279				aura_public_key: AuraPublicKey(
280					hex!("bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec").into(),
281				),
282				grandpa_public_key: GrandpaPublicKey(
283					hex!("9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d").into(),
284				),
285			},
286			PermissionedCandidateDatumV0 {
287				sidechain_public_key: SidechainPublicKey(
288					hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf").into(),
289				),
290				aura_public_key: AuraPublicKey(
291					hex!("56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19").into(),
292				),
293				grandpa_public_key: GrandpaPublicKey(
294					hex!("7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32").into(),
295				),
296			},
297		]);
298
299		assert_eq!(PermissionedCandidateDatums::try_from(plutus_data).unwrap(), expected_datum)
300	}
301
302	fn v0_datum_json() -> serde_json::Value {
303		serde_json::json!({
304			"list": [
305				{ "constructor": 0, "fields": [] },
306				{ "list": [
307					{"list": [
308						{"bytes": "cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"},
309						{"bytes": "bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec"},
310						{"bytes": "9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d"}
311					]},
312					{"list": [
313						{"bytes": "79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf"},
314						{"bytes": "56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19"},
315						{"bytes": "7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32"}
316					]}
317				]},
318				{ "int": 0 }
319			]
320		})
321	}
322
323	fn v1_datum_json() -> serde_json::Value {
324		serde_json::json!({
325			"list": [
326				{ "constructor": 0, "fields": [] },
327				{"list": [
328					{"list":[
329						{"bytes": "cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"},
330						{"list": [
331							{"list": [{"bytes": hex::encode(b"aura")}, {"bytes": "bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec"}]},
332							{"list": [{"bytes": hex::encode(b"gran")}, {"bytes": "9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d"}]}
333						]}
334					]},
335					{"list":[
336						{"bytes": "79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf"},
337						{"list": [
338							{"list": [{"bytes": hex::encode(b"aura")}, {"bytes": "56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19"}]},
339							{"list": [{"bytes": hex::encode(b"gran")}, {"bytes": "7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32"}]}
340						]}
341					]}
342				]},
343				{ "int": 1 }
344			]
345		})
346	}
347
348	#[test]
349	fn permissioned_candidates_to_plutus_data_outputs_v0_for_aura_and_grandpa_keys() {
350		let expected_plutus_data = json_to_plutus_data(v0_datum_json());
351
352		let domain_data = vec![
353			PermissionedCandidateData {
354				sidechain_public_key: SidechainPublicKey(
355					hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854")
356						.to_vec(),
357				),
358				keys: CandidateKeys(vec![
359					AuraPublicKey(
360						hex!("bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec")
361							.to_vec(),
362					)
363					.into(),
364					GrandpaPublicKey(
365						hex!("9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d")
366							.to_vec(),
367					)
368					.into(),
369				]),
370			},
371			PermissionedCandidateData {
372				sidechain_public_key: SidechainPublicKey(
373					hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf")
374						.to_vec(),
375				),
376				keys: CandidateKeys(vec![
377					AuraPublicKey(
378						hex!("56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19")
379							.to_vec(),
380					)
381					.into(),
382					GrandpaPublicKey(
383						hex!("7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32")
384							.to_vec(),
385					)
386					.into(),
387				]),
388			},
389		];
390		assert_eq!(permissioned_candidates_to_plutus_data(&domain_data), expected_plutus_data)
391	}
392
393	#[test]
394	fn permissioned_candidates_to_plutus_data_outputs_v1() {
395		let domain_data = vec![
396			PermissionedCandidateData {
397				sidechain_public_key: SidechainPublicKey([1; 33].to_vec()),
398				keys: CandidateKeys(vec![
399					CandidateKey { id: [2; 4], bytes: [3; 32].to_vec() },
400					CandidateKey { id: [4; 4], bytes: [5; 32].to_vec() },
401				]),
402			},
403			PermissionedCandidateData {
404				sidechain_public_key: SidechainPublicKey([6; 33].to_vec()),
405				keys: CandidateKeys(vec![
406					CandidateKey { id: [7; 4], bytes: [8; 32].to_vec() },
407					CandidateKey { id: [9; 4], bytes: [10u8; 32].to_vec() },
408				]),
409			},
410		];
411		let json = serde_json::json!({
412			"list": [
413				{ "constructor": 0, "fields": [] },
414				{"list": [
415					{"list":[
416						{"bytes": "010101010101010101010101010101010101010101010101010101010101010101"},
417						{"list": [
418							{"list": [{"bytes": "02020202"}, {"bytes": "0303030303030303030303030303030303030303030303030303030303030303"}]},
419							{"list": [{"bytes": "04040404"}, {"bytes": "0505050505050505050505050505050505050505050505050505050505050505"}]}
420						]}
421					]},
422					{"list":[
423						{"bytes": "060606060606060606060606060606060606060606060606060606060606060606"},
424						{"list": [
425							{"list": [{"bytes": "07070707"}, {"bytes": "0808080808080808080808080808080808080808080808080808080808080808"}]},
426							{"list": [{"bytes": "09090909"}, {"bytes": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"}]}
427						]}
428					]}
429				]},
430				{ "int": 1 }
431			]
432		});
433		let expected_plutus_data = json_to_plutus_data(json);
434		assert_eq!(permissioned_candidates_to_plutus_data(&domain_data), expected_plutus_data)
435	}
436
437	#[test]
438	fn valid_v0_permissioned_candidates() {
439		let plutus_data = json_to_plutus_data(v0_datum_json());
440
441		let expected_datum = PermissionedCandidateDatums::V0(vec![
442			PermissionedCandidateDatumV0 {
443				sidechain_public_key: SidechainPublicKey(
444					hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854").into(),
445				),
446				aura_public_key: AuraPublicKey(
447					hex!("bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec").into(),
448				),
449				grandpa_public_key: GrandpaPublicKey(
450					hex!("9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d").into(),
451				),
452			},
453			PermissionedCandidateDatumV0 {
454				sidechain_public_key: SidechainPublicKey(
455					hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf").into(),
456				),
457				aura_public_key: AuraPublicKey(
458					hex!("56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19").into(),
459				),
460				grandpa_public_key: GrandpaPublicKey(
461					hex!("7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32").into(),
462				),
463			},
464		]);
465
466		assert_eq!(PermissionedCandidateDatums::try_from(plutus_data).unwrap(), expected_datum)
467	}
468
469	#[test]
470	fn valid_v1_permissioned_candidates() {
471		let plutus_data = json_to_plutus_data(v1_datum_json());
472
473		let expected_datum = PermissionedCandidateDatums::V1(vec![
474			PermissionedCandidateDatumV1 {
475				partner_chains_key: SidechainPublicKey(
476					hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854").into(),
477				),
478				keys: CandidateKeys(vec![
479					CandidateKey::new(
480						AURA,
481						hex!("bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec")
482							.into(),
483					),
484					CandidateKey::new(
485						GRANDPA,
486						hex!("9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d")
487							.into(),
488					),
489				]),
490			},
491			PermissionedCandidateDatumV1 {
492				partner_chains_key: SidechainPublicKey(
493					hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf").into(),
494				),
495				keys: CandidateKeys(vec![
496					CandidateKey::new(
497						AURA,
498						hex!("56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19")
499							.into(),
500					),
501					CandidateKey::new(
502						GRANDPA,
503						hex!("7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32")
504							.into(),
505					),
506				]),
507			},
508		]);
509
510		assert_eq!(PermissionedCandidateDatums::try_from(plutus_data).unwrap(), expected_datum)
511	}
512}