partner_chains_cli/
permissioned_candidates.rs

1use crate::cmd_traits::{GetPermissionedCandidates, UpsertPermissionedCandidates};
2use authority_selection_inherents::MaybeFromCandidateKeys;
3use ogmios_client::query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId};
4use ogmios_client::query_network::QueryNetwork;
5use ogmios_client::transactions::Transactions;
6use partner_chains_cardano_offchain::await_tx::FixedDelayRetries;
7use partner_chains_cardano_offchain::cardano_keys::CardanoPaymentSigningKey;
8use partner_chains_cardano_offchain::csl::NetworkTypeExt;
9use partner_chains_cardano_offchain::multisig::MultiSigSmartContractResult;
10use partner_chains_cardano_offchain::permissioned_candidates::{
11	get_permissioned_candidates, upsert_permissioned_candidates,
12};
13use serde::{Deserialize, Deserializer, Serialize};
14use sidechain_domain::byte_string::ByteString;
15use sidechain_domain::{CandidateKey, CandidateKeys, PermissionedCandidateData, UtxoId};
16use sp_core::crypto::AccountId32;
17use sp_core::ecdsa;
18use sp_runtime::traits::{IdentifyAccount, OpaqueKeys};
19use std::collections::BTreeMap;
20use std::fmt::{Display, Formatter};
21
22/// Struct that holds permissioned candidates keys in raw string-ish formats
23#[derive(Debug, Serialize, Eq, PartialEq)]
24pub struct PermissionedCandidateKeys {
25	/// 0x prefixed hex representation of the ECDSA public key
26	pub partner_chains_key: ByteString,
27	/// Keys are text representation of key type id, values are key bytes
28	pub keys: BTreeMap<String, ByteString>,
29}
30
31impl PermissionedCandidateKeys {
32	fn keys_sorted(&self) -> Vec<(String, ByteString)> {
33		let mut v: Vec<_> = self.keys.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
34		v.sort();
35		v
36	}
37}
38
39impl Display for PermissionedCandidateKeys {
40	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41		write!(f, "Partner Chains Key: {}", self.partner_chains_key.to_hex_string())?;
42		for (id, bytes) in self.keys_sorted().iter() {
43			write!(f, ", {id}: {}", bytes.to_hex_string())?
44		}
45		Ok(())
46	}
47}
48
49impl From<&sidechain_domain::PermissionedCandidateData> for PermissionedCandidateKeys {
50	fn from(value: &sidechain_domain::PermissionedCandidateData) -> Self {
51		Self {
52			partner_chains_key: ByteString::from(value.sidechain_public_key.0.clone()),
53			keys: value
54				.keys
55				.0
56				.iter()
57				.map(|ck| {
58					(
59						String::from_utf8(ck.id.to_vec()).expect("key type ids are valid utf-8"),
60						ByteString::from(ck.bytes.clone()),
61					)
62				})
63				.collect(),
64		}
65	}
66}
67
68impl TryFrom<&PermissionedCandidateKeys> for sidechain_domain::CandidateKeys {
69	type Error = String;
70
71	fn try_from(value: &PermissionedCandidateKeys) -> Result<Self, Self::Error> {
72		let mut acc = vec![];
73		for (k, v) in value.keys.iter() {
74			let id: [u8; 4] = k
75				.as_bytes()
76				.try_into()
77				.map_err(|_| format!("Could not parse key type id: '{k}'"))?;
78			let bytes = v.0.clone();
79			acc.push(CandidateKey { id, bytes });
80		}
81		Ok(CandidateKeys(acc))
82	}
83}
84
85/// Groups together keys of permissioned candidates.
86#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
87pub struct ParsedPermissionedCandidatesKeys<Keys> {
88	/// Polkadot identity of the permissioned candidate (aka. partner-chain key)
89	pub sidechain: ecdsa::Public,
90	/// Keys of the candidate, other than the partner chain key
91	pub keys: Keys,
92}
93
94impl<T> ParsedPermissionedCandidatesKeys<T> {
95	/// Permissioned Candidate partner-chain (sidechain) key mapped to AccountId32
96	pub fn account_id_32(&self) -> AccountId32 {
97		sp_runtime::MultiSigner::from(self.sidechain).into_account()
98	}
99}
100
101impl<Keys: MaybeFromCandidateKeys> TryFrom<&PermissionedCandidateKeys>
102	for ParsedPermissionedCandidatesKeys<Keys>
103{
104	type Error = anyhow::Error;
105
106	fn try_from(value: &PermissionedCandidateKeys) -> Result<Self, Self::Error> {
107		let sidechain = parse_ecdsa(&value.partner_chains_key.0).ok_or(anyhow::Error::msg(
108			format!("'{}' is invalid ECDSA public key", value.partner_chains_key.to_hex_string()),
109		))?;
110		let candidate_keys = CandidateKeys::try_from(value).map_err(|e| anyhow::anyhow!(e))?;
111		let keys = MaybeFromCandidateKeys::maybe_from(&candidate_keys)
112			.ok_or(anyhow::anyhow!("Could not parse candidate keys!"))?;
113		Ok(Self { sidechain, keys })
114	}
115}
116
117impl<Keys: OpaqueKeys> From<&ParsedPermissionedCandidatesKeys<Keys>>
118	for sidechain_domain::PermissionedCandidateData
119{
120	fn from(value: &ParsedPermissionedCandidatesKeys<Keys>) -> Self {
121		let keys = Keys::key_ids()
122			.iter()
123			.map(|key_id| {
124				let bytes = value.keys.get_raw(*key_id).to_vec();
125				CandidateKey { id: key_id.0, bytes }
126			})
127			.collect();
128		Self {
129			sidechain_public_key: sidechain_domain::SidechainPublicKey(value.sidechain.0.to_vec()),
130			keys: CandidateKeys(keys),
131		}
132	}
133}
134
135fn parse_ecdsa(bytes: &[u8]) -> Option<ecdsa::Public> {
136	Some(ecdsa::Public::from(<[u8; 33]>::try_from(bytes).ok()?))
137}
138
139impl<C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId>
140	UpsertPermissionedCandidates for C
141{
142	async fn upsert_permissioned_candidates(
143		&self,
144		await_tx: FixedDelayRetries,
145		genesis_utxo: UtxoId,
146		candidates: &[PermissionedCandidateData],
147		payment_signing_key: &CardanoPaymentSigningKey,
148	) -> anyhow::Result<Option<MultiSigSmartContractResult>> {
149		upsert_permissioned_candidates(
150			genesis_utxo,
151			candidates,
152			payment_signing_key,
153			self,
154			&await_tx,
155		)
156		.await
157	}
158}
159
160impl<C: QueryLedgerState + QueryNetwork> GetPermissionedCandidates for C {
161	async fn get_permissioned_candidates(
162		&self,
163		genesis_utxo: UtxoId,
164	) -> anyhow::Result<Option<Vec<PermissionedCandidateData>>> {
165		let network = self.shelley_genesis_configuration().await?.network.to_csl();
166		get_permissioned_candidates(genesis_utxo, network, self).await
167	}
168}
169
170/// Backward compatible way of reading user keys, to not crash wizards when running against files created with some previous version
171impl<'de> Deserialize<'de> for PermissionedCandidateKeys {
172	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
173	where
174		D: Deserializer<'de>,
175	{
176		CandidateKeysFormat::deserialize(deserializer).map(From::from)
177	}
178}
179
180#[derive(Deserialize)]
181#[serde(untagged)]
182enum CandidateKeysFormat {
183	V1 { partner_chains_key: ByteString, keys: BTreeMap<String, ByteString> },
184	V0 { sidechain_pub_key: ByteString, aura_pub_key: ByteString, grandpa_pub_key: ByteString },
185}
186
187impl From<CandidateKeysFormat> for PermissionedCandidateKeys {
188	fn from(v: CandidateKeysFormat) -> Self {
189		match v {
190			CandidateKeysFormat::V1 { partner_chains_key, keys } => {
191				Self { partner_chains_key, keys }
192			},
193			CandidateKeysFormat::V0 { sidechain_pub_key, aura_pub_key, grandpa_pub_key } => Self {
194				partner_chains_key: sidechain_pub_key,
195				keys: vec![("aura".to_owned(), aura_pub_key), ("gran".to_owned(), grandpa_pub_key)]
196					.into_iter()
197					.collect(),
198			},
199		}
200	}
201}
202
203#[cfg(test)]
204mod tests {
205	use sidechain_domain::byte_string::ByteString;
206
207	use super::PermissionedCandidateKeys;
208
209	#[test]
210	fn candidate_keys_serde_round_trip() {
211		let keys = PermissionedCandidateKeys {
212			partner_chains_key: ByteString::from_hex_unsafe("0x010101"),
213			keys: vec![
214				("key1".to_owned(), ByteString::from_hex_unsafe("0x020202")),
215				("key2".to_owned(), ByteString::from_hex_unsafe("0x030303")),
216			]
217			.into_iter()
218			.collect(),
219		};
220		let json = serde_json::to_value(&keys).unwrap();
221		let deserialized = serde_json::from_value(json).unwrap();
222		assert_eq!(keys, deserialized)
223	}
224
225	#[test]
226	fn v0_deserialization() {
227		let deserialized: PermissionedCandidateKeys = serde_json::from_value(serde_json::json!({
228			"sidechain_pub_key": "0x0101",
229			"aura_pub_key": "0x0202",
230			"grandpa_pub_key": "0x0303"
231		}))
232		.unwrap();
233		assert_eq!(
234			deserialized,
235			PermissionedCandidateKeys {
236				partner_chains_key: ByteString::from_hex_unsafe("0x0101"),
237				keys: vec![
238					("aura".to_owned(), ByteString::from_hex_unsafe("0x0202")),
239					("gran".to_owned(), ByteString::from_hex_unsafe("0x0303")),
240				]
241				.into_iter()
242				.collect(),
243			}
244		)
245	}
246}