cli_commands/
block_producer_metadata_signatures.rs1use crate::key_params::CrossChainSigningKeyParam;
2use anyhow::anyhow;
3use byte_string::ByteString;
4use clap::Parser;
5use parity_scale_codec::Encode;
6use serde::de::DeserializeOwned;
7use serde_json::{self, json};
8use sidechain_domain::*;
9use sp_block_producer_metadata::MetadataSignedMessage;
10use std::io::{BufReader, Read};
11
12#[derive(Clone, Debug, Parser)]
13#[command(author, version, about, long_about = None)]
14pub struct BlockProducerMetadataSignatureCmd {
15 #[arg(long)]
17 pub genesis_utxo: UtxoId,
18 #[arg(long)]
20 pub metadata_file: String,
21 #[arg(long)]
23 pub cross_chain_signing_key: CrossChainSigningKeyParam,
24}
25
26impl BlockProducerMetadataSignatureCmd {
27 pub fn execute<M: Send + Sync + DeserializeOwned + Encode>(&self) -> anyhow::Result<()> {
28 let file = std::fs::File::open(self.metadata_file.clone())
29 .map_err(|err| anyhow!("Failed to open file {}: {err}", self.metadata_file))?;
30 let metadata_reader = BufReader::new(file);
31 let output = self.get_output::<M>(metadata_reader)?;
32
33 println!("{}", serde_json::to_string_pretty(&output)?);
34
35 Ok(())
36 }
37
38 pub fn get_output<M: Send + Sync + DeserializeOwned + Encode>(
39 &self,
40 metadata_reader: impl Read,
41 ) -> anyhow::Result<serde_json::Value> {
42 let metadata: M = serde_json::from_reader(metadata_reader).map_err(|err| {
43 anyhow!("Failed to parse metadata: {err}. Metadata should be in JSON format.",)
44 })?;
45 let encoded_metadata = metadata.encode();
46 let message = MetadataSignedMessage {
47 cross_chain_pub_key: self.cross_chain_signing_key.vkey(),
48 metadata,
49 genesis_utxo: self.genesis_utxo,
50 };
51 let signature = message.sign_with_key(&self.cross_chain_signing_key.0);
52
53 Ok(json!({
54 "signature": signature,
55 "cross_chain_pub_key": self.cross_chain_signing_key.vkey(),
56 "cross_chain_pub_key_hash": self.cross_chain_signing_key.vkey().hash(),
57 "encoded_metadata": ByteString(encoded_metadata),
58 "encoded_message": ByteString(message.encode())
59 }))
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::key_params::CrossChainSigningKeyParam;
67 use hex_literal::hex;
68 use pretty_assertions::assert_eq;
69 use serde::Deserialize;
70 use serde_json::json;
71 use sidechain_domain::UtxoId;
72
73 #[derive(Deserialize, Encode)]
74 struct TestMetadata {
75 url: String,
76 hash: String,
77 }
78
79 #[test]
80 fn produces_correct_json_output_with_signature_and_pubkey() {
81 let cmd = BlockProducerMetadataSignatureCmd {
82 genesis_utxo: UtxoId::new([1; 32], 1),
83 metadata_file: "unused".to_string(),
84 cross_chain_signing_key: CrossChainSigningKeyParam(
85 k256::SecretKey::from_slice(
86 &hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"),
88 )
89 .unwrap(),
90 ),
91 };
92
93 let metadata_json = json!({
94 "url": "http://example.com",
95 "hash": "1234"
96 });
97 let metadata = serde_json::to_string(&metadata_json).unwrap();
98 let metadata_reader = BufReader::new(metadata.as_bytes());
99
100 let output = cmd.get_output::<TestMetadata>(metadata_reader).unwrap();
101
102 let expected_output = json!({
103 "cross_chain_pub_key": "0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1",
104 "cross_chain_pub_key_hash" : "0x4a20b7cab322b36838a8e4b6063c3563cdb79c97175f6c2d233dac4d",
105 "encoded_message": "0x84020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a148687474703a2f2f6578616d706c652e636f6d103132333401010101010101010101010101010101010101010101010101010101010101010100",
106 "signature": "0xf86d3aa75a6a8bda35dfdd2472b8e5f2f95446e4542ab0adb6f3e7681f01b74060082c0debfb9616a54f88cf42b88e1a2f43c75dc4394bfdde33972deb491fcb",
107 "encoded_metadata": "0x48687474703a2f2f6578616d706c652e636f6d1031323334"
108 });
109
110 assert_eq!(output, expected_output)
111 }
112
113 #[test]
114 fn retuns_error_for_invalid_json() {
115 let cmd = BlockProducerMetadataSignatureCmd {
116 genesis_utxo: UtxoId::new([1; 32], 1),
117 metadata_file: "unused".to_string(),
118 cross_chain_signing_key: CrossChainSigningKeyParam(
119 k256::SecretKey::from_slice(
120 &hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"),
122 )
123 .unwrap(),
124 ),
125 };
126
127 let metadata_reader = BufReader::new("{ invalid json }".as_bytes());
128
129 let output = cmd.get_output::<TestMetadata>(metadata_reader);
130
131 assert!(output.is_err(), "{:?} should be Err", output);
132 }
133}