diff --git a/Cargo.lock b/Cargo.lock index 5cc0b82b5..2f88aea4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,7 +511,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.39", + "rustix 0.38.40", "slab", "tracing", "windows-sys 0.52.0", @@ -563,7 +563,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.0", "futures-lite", - "rustix 0.38.39", + "rustix 0.38.40", "tracing", "windows-sys 0.52.0", ] @@ -580,7 +580,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.39", + "rustix 0.38.40", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -2755,6 +2755,8 @@ dependencies = [ "entropy-client", "entropy-shared", "hex", + "serde", + "serde_json", "sp-core 31.0.0", "sp-runtime 32.0.0", "subxt", @@ -3704,7 +3706,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.39", + "rustix 0.38.40", "windows-sys 0.48.0", ] @@ -5932,7 +5934,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.39", + "rustix 0.38.40", ] [[package]] @@ -8148,7 +8150,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite 0.2.14", - "rustix 0.38.39", + "rustix 0.38.40", "tracing", "windows-sys 0.52.0", ] @@ -9122,9 +9124,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.5.0", "errno", @@ -14252,7 +14254,7 @@ dependencies = [ "cfg-if", "fastrand", "once_cell", - "rustix 0.38.39", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -14271,7 +14273,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.39", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -14415,9 +14417,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -15641,7 +15643,7 @@ dependencies = [ "directories-next", "file-per-thread-logger 0.2.0", "log", - "rustix 0.38.39", + "rustix 0.38.40", "serde", "sha2 0.10.8", "toml 0.5.11", @@ -15796,7 +15798,7 @@ checksum = "fc8c8410c03a79073ea06806ccde3da4854c646bd646b3b2707b99b3746c3f70" dependencies = [ "cc", "cfg-if", - "rustix 0.38.39", + "rustix 0.38.40", "wasmtime-asm-macros 12.0.2", "wasmtime-versioned-export-macros", "windows-sys 0.48.0", @@ -15842,7 +15844,7 @@ dependencies = [ "log", "object 0.31.1", "rustc-demangle", - "rustix 0.38.39", + "rustix 0.38.40", "serde", "target-lexicon", "wasmtime-environ 12.0.2", @@ -15871,7 +15873,7 @@ checksum = "aef27ea6c34ef888030d15560037fe7ef27a5609fbbba8e1e3e41dc4245f5bb2" dependencies = [ "object 0.31.1", "once_cell", - "rustix 0.38.39", + "rustix 0.38.40", "wasmtime-versioned-export-macros", ] @@ -15939,7 +15941,7 @@ dependencies = [ "memoffset 0.9.1", "paste", "rand", - "rustix 0.38.39", + "rustix 0.38.40", "sptr", "wasm-encoder 0.31.1", "wasmtime-asm-macros 12.0.2", @@ -16074,7 +16076,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.39", + "rustix 0.38.40", ] [[package]] diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs index 27770df46..ab1023e0b 100644 --- a/crates/protocol/src/lib.rs +++ b/crates/protocol/src/lib.rs @@ -33,7 +33,7 @@ use std::{ use blake2::{Blake2s256, Digest}; use errors::{ProtocolExecutionErr, VerifyingKeyError}; -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; use sp_core::{sr25519, Pair}; use subxt::utils::AccountId32; use synedrion::{ @@ -148,6 +148,19 @@ pub struct RecoverableSignature { pub recovery_id: RecoveryId, } +// This cannot be derived because [RecoveryId] does not implement Serialize +impl Serialize for RecoverableSignature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("RecoverableSignature", 2)?; + state.serialize_field("signature", &self.signature)?; + state.serialize_field("recovery_id", &self.recovery_id.to_byte())?; + state.end() + } +} + impl RecoverableSignature { pub fn to_rsv_bytes(&self) -> [u8; 65] { let mut res = [0u8; 65]; diff --git a/crates/test-cli/Cargo.toml b/crates/test-cli/Cargo.toml index c78d3b3fc..b74a18513 100644 --- a/crates/test-cli/Cargo.toml +++ b/crates/test-cli/Cargo.toml @@ -21,3 +21,5 @@ bincode ="1.3.3" x25519-dalek ="2.0.1" sp-runtime ={ version="32.0.0", default-features=false } entropy-shared={ version="0.3.0", path="../shared" } +serde_json ="1.0.132" +serde ={ version="1.0.215", features=["derive"] } diff --git a/crates/test-cli/src/lib.rs b/crates/test-cli/src/lib.rs index 6a9007861..b0c1ba79d 100644 --- a/crates/test-cli/src/lib.rs +++ b/crates/test-cli/src/lib.rs @@ -20,7 +20,9 @@ use colored::Colorize; use entropy_client::{ chain_api::{ entropy::runtime_types::{ - bounded_collections::bounded_vec::BoundedVec, pallet_registry::pallet::ProgramInstance, + bounded_collections::bounded_vec::BoundedVec, + pallet_programs::pallet::ProgramInfo, + pallet_registry::pallet::{ProgramInstance, RegisteredInfo}, }, EntropyConfig, }, @@ -32,7 +34,7 @@ use entropy_client::{ }; pub use entropy_shared::PROGRAM_VERSION_NUMBER; use sp_core::{sr25519, Hasher, Pair}; -use sp_runtime::traits::BlakeTwo256; +use sp_runtime::{traits::BlakeTwo256, Serialize}; use std::{fs, path::PathBuf}; use subxt::{ backend::legacy::LegacyRpcMethods, @@ -46,7 +48,7 @@ use subxt::{ about = "CLI tool for testing Entropy", long_about = "This is a CLI test client.\nIt requires a running deployment of Entropy with at least two chain nodes and two TSS servers." )] -struct Cli { +pub struct Cli { #[clap(subcommand)] command: CliCommand, /// The chain endpoint to use. @@ -57,6 +59,9 @@ struct Cli { /// priority. #[arg(short, long)] chain_endpoint: Option, + /// Whether to give command output as JSON. Defaults to false. + #[arg(short, long)] + pub json: bool, } #[derive(Subcommand, Debug, Clone)] @@ -186,15 +191,22 @@ enum CliCommand { }, } +impl Cli { + fn log(&self, text: String) { + if !self.json { + println!("{text}"); + } + } +} + pub async fn run_command( + cli: Cli, program_file_option: Option, config_interface_file_option: Option, aux_data_interface_file_option: Option, program_version_number_option: Option, ) -> anyhow::Result { - let cli = Cli::parse(); - - let endpoint_addr = cli.chain_endpoint.unwrap_or_else(|| { + let endpoint_addr = cli.chain_endpoint.clone().unwrap_or_else(|| { std::env::var("ENTROPY_DEVNET").unwrap_or("ws://localhost:9944".to_string()) }); @@ -203,7 +215,7 @@ pub async fn run_command( let api = get_api(&endpoint_addr).await?; let rpc = get_rpc(&endpoint_addr).await?; - match cli.command { + match cli.command.clone() { CliCommand::Register { mnemonic_option, programs, program_version_numbers } => { let mnemonic = if let Some(mnemonic_option) = mnemonic_option { mnemonic_option @@ -213,7 +225,7 @@ pub async fn run_command( let program_keypair = ::from_string(&mnemonic, None)?; let program_account = SubxtAccountId32(program_keypair.public().0); - println!("Program account: {}", program_keypair.public()); + cli.log(format!("Program account: {}", program_keypair.public())); let mut programs_info = vec![]; @@ -242,7 +254,12 @@ pub async fn run_command( ) .await?; - Ok(format!("Verifying key: {},\n{:?}", hex::encode(verifying_key), registered_info)) + let verifying_key = hex::encode(verifying_key); + if cli.json { + Ok(serde_json::to_string_pretty(&verifying_key)?) + } else { + Ok(format!("Verifying key: {},\n{:?}", verifying_key, registered_info)) + } }, CliCommand::Sign { signature_verifying_key, message, auxilary_data, mnemonic_option } => { let mnemonic = if let Some(mnemonic_option) = mnemonic_option { @@ -253,7 +270,7 @@ pub async fn run_command( // If an account name is not provided, use the Alice key let user_keypair = ::from_string(&mnemonic, None)?; - println!("User account for current call: {}", user_keypair.public()); + cli.log(format!("User account for current call: {}", user_keypair.public())); let auxilary_data = if let Some(data) = auxilary_data { Some(hex::decode(data)?) } else { None }; @@ -272,7 +289,12 @@ pub async fn run_command( auxilary_data, ) .await?; - Ok(format!("Message signed: {:?}", recoverable_signature)) + + if cli.json { + Ok(serde_json::to_string_pretty(&recoverable_signature)?) + } else { + Ok(format!("Message signed: {:?}", recoverable_signature)) + } }, CliCommand::StoreProgram { mnemonic_option, @@ -287,7 +309,7 @@ pub async fn run_command( passed_mnemonic.expect("No Mnemonic set") }; let keypair = ::from_string(&mnemonic, None)?; - println!("Storing program using account: {}", keypair.public()); + cli.log(format!("Storing program using account: {}", keypair.public())); let program = match program_file { Some(file_name) => fs::read(file_name)?, @@ -324,7 +346,14 @@ pub async fn run_command( program_version_number, ) .await?; - Ok(format!("Program stored: {}", hex::encode(hash))) + + let hash = hex::encode(hash); + + if cli.json { + Ok(serde_json::to_string_pretty(&hash)?) + } else { + Ok(format!("Program stored: {}", hex::encode(hash))) + } }, CliCommand::RemoveProgram { mnemonic_option, hash } => { let mnemonic = if let Some(mnemonic_option) = mnemonic_option { @@ -333,7 +362,7 @@ pub async fn run_command( passed_mnemonic.expect("No Mnemonic set") }; let keypair = ::from_string(&mnemonic, None)?; - println!("Removing program using account: {}", keypair.public()); + cli.log(format!("Removing program using account: {}", keypair.public())); let hash: [u8; 32] = hex::decode(hash)? .try_into() @@ -341,7 +370,11 @@ pub async fn run_command( remove_program(&api, &rpc, &keypair, H256(hash)).await?; - Ok("Program removed".to_string()) + if cli.json { + Ok("{}".to_string()) + } else { + Ok("Program removed".to_string()) + } }, CliCommand::UpdatePrograms { signature_verifying_key, @@ -355,7 +388,7 @@ pub async fn run_command( passed_mnemonic.expect("No Mnemonic set") }; let program_keypair = ::from_string(&mnemonic, None)?; - println!("Program account: {}", program_keypair.public()); + cli.log(format!("Program account: {}", program_keypair.public())); let mut programs_info = Vec::new(); @@ -382,68 +415,75 @@ pub async fn run_command( update_programs(&api, &rpc, verifying_key, &program_keypair, BoundedVec(programs_info)) .await?; - Ok("Programs updated".to_string()) + if cli.json { + Ok("{}".to_string()) + } else { + Ok("Programs updated".to_string()) + } }, CliCommand::Status => { let accounts = get_accounts(&api, &rpc).await?; - println!( - "There are {} registered Entropy accounts.\n", - accounts.len().to_string().green() - ); - if !accounts.is_empty() { + let programs = get_programs(&api, &rpc).await?; + + if !cli.json { println!( - "{:<64} {:<12} Programs:", - "Verifying key:".green(), - "Visibility:".purple(), + "There are {} registered Entropy accounts.\n", + accounts.len().to_string().green() ); - for (account_id, info) in accounts { - println!( - "{} {}", - hex::encode(account_id).green(), - format!( - "{:?}", - info.programs_data - .0 - .iter() - .map(|program_instance| format!( - "{}", - program_instance.program_pointer - )) - .collect::>() - ) - .white(), - ); + if !accounts.is_empty() { + println!("{:<66} Programs:", "Verifying key:".green()); + for (account_id, info) in accounts.iter() { + println!( + "{} {}", + hex::encode(account_id).green(), + format!( + "{:?}", + info.programs_data + .0 + .iter() + .map(|program_instance| format!( + "{}", + program_instance.program_pointer + )) + .collect::>() + ) + .white(), + ); + } } - } - let programs = get_programs(&api, &rpc).await?; - - println!("\nThere are {} stored programs\n", programs.len().to_string().green()); + println!("\nThere are {} stored programs\n", programs.len().to_string().green()); - if !programs.is_empty() { - println!( - "{:<64} {:<48} {:<11} {:<14} {} {}", - "Hash".blue(), - "Stored by:".green(), - "Times used:".purple(), - "Size in bytes:".cyan(), - "Configurable?".yellow(), - "Has auxiliary?".yellow(), - ); - for (hash, program_info) in programs { + if !programs.is_empty() { println!( - "{} {} {:>11} {:>14} {:<13} {}", - hex::encode(hash), - program_info.deployer, - program_info.ref_counter, - program_info.bytecode.len(), - !program_info.configuration_schema.is_empty(), - !program_info.auxiliary_data_schema.is_empty(), + "{:<64} {:<48} {:<11} {:<14} {} {}", + "Hash".blue(), + "Stored by:".green(), + "Times used:".purple(), + "Size in bytes:".cyan(), + "Configurable?".yellow(), + "Has auxiliary?".yellow(), ); + for (hash, program_info) in programs.iter() { + println!( + "{} {} {:>11} {:>14} {:<13} {}", + hex::encode(hash), + program_info.deployer, + program_info.ref_counter, + program_info.bytecode.len(), + !program_info.configuration_schema.is_empty(), + !program_info.auxiliary_data_schema.is_empty(), + ); + } } } - Ok("Got status".to_string()) + if cli.json { + let output = StatusOutput::new(accounts, programs); + Ok(serde_json::to_string_pretty(&output)?) + } else { + Ok("Got status".to_string()) + } }, CliCommand::ChangeEndpoint { new_endpoint, quote, mnemonic_option } => { let mnemonic = if let Some(mnemonic_option) = mnemonic_option { @@ -453,12 +493,17 @@ pub async fn run_command( }; let user_keypair = ::from_string(&mnemonic, None)?; - println!("User account for current call: {}", user_keypair.public()); + cli.log(format!("User account for current call: {}", user_keypair.public())); let result_event = change_endpoint(&api, &rpc, user_keypair, new_endpoint, quote.into()).await?; - println!("Event result: {:?}", result_event); - Ok("Endpoint changed".to_string()) + cli.log(format!("Event result: {:?}", result_event)); + + if cli.json { + Ok("{}".to_string()) + } else { + Ok("Endpoint changed".to_string()) + } }, CliCommand::ChangeThresholdAccounts { new_tss_account, @@ -473,7 +518,7 @@ pub async fn run_command( passed_mnemonic.expect("No Mnemonic set") }; let user_keypair = ::from_string(&mnemonic, None)?; - println!("User account for current call: {}", user_keypair.public()); + cli.log(format!("User account for current call: {}", user_keypair.public())); let new_pck_certificate_chain = new_pck_certificate_chain.iter().cloned().map(|i| i.into()).collect::<_>(); @@ -487,9 +532,13 @@ pub async fn run_command( quote.into(), ) .await?; - println!("Event result: {:?}", result_event); + cli.log(format!("Event result: {:?}", result_event)); - Ok("Threshold accounts changed".to_string()) + if cli.json { + Ok("{}".to_string()) + } else { + Ok("Threshold accounts changed".to_string()) + } }, CliCommand::JumpstartNetwork { mnemonic_option } => { let mnemonic = if let Some(mnemonic_option) = mnemonic_option { @@ -499,11 +548,15 @@ pub async fn run_command( }; let signer = ::from_string(&mnemonic, None)?; - println!("Account being used for jumpstart: {}", signer.public()); + cli.log(format!("Account being used for jumpstart: {}", signer.public())); jumpstart_network(&api, &rpc, signer).await?; - Ok("Succesfully jumpstarted network.".to_string()) + if cli.json { + Ok("{}".to_string()) + } else { + Ok("Succesfully jumpstarted network.".to_string()) + } }, } } @@ -601,7 +654,7 @@ impl Program { Ok(hash) => Ok(Self::new(hash, configuration)), Err(error) => { if error.to_string().ends_with("ProgramAlreadySet") { - println!("Program is already stored - using existing one"); + // Use existing program as it is already stored let hash = BlakeTwo256::hash(&program_bytecode); Ok(Self::new(H256(hash.into()), configuration)) } else { @@ -611,3 +664,25 @@ impl Program { } } } + +#[derive(Serialize)] +/// Output from the status command +struct StatusOutput { + accounts: Vec, + programs: Vec, +} + +impl StatusOutput { + fn new( + accounts: Vec<([u8; 33], RegisteredInfo)>, + programs: Vec<(H256, ProgramInfo)>, + ) -> Self { + let accounts = accounts + .into_iter() + .map(|(verifying_key, _registered_info)| hex::encode(verifying_key)) + .collect(); + let programs = + programs.into_iter().map(|(hash, _program_info)| hex::encode(hash.0)).collect(); + Self { accounts, programs } + } +} diff --git a/crates/test-cli/src/main.rs b/crates/test-cli/src/main.rs index e554a995e..5e176d074 100644 --- a/crates/test-cli/src/main.rs +++ b/crates/test-cli/src/main.rs @@ -14,22 +14,30 @@ // along with this program. If not, see . //! Simple CLI to test registering, updating programs and signing -use std::time::Instant; - +use clap::Parser; use colored::Colorize; -use entropy_test_cli::run_command; +use entropy_test_cli::{run_command, Cli}; +use std::time::Instant; #[tokio::main] async fn main() -> anyhow::Result<()> { let now = Instant::now(); - match run_command(None, None, None, None).await { + let cli = Cli::parse(); + let json_ouput = cli.json; + match run_command(cli, None, None, None, None).await { Ok(output) => { - println!("Success: {}", output.green()); - println!("{}", format!("That took {:?}", now.elapsed()).yellow()); + if json_ouput { + println!("{}", output); + } else { + println!("Success: {}", output.green()); + println!("{}", format!("That took {:?}", now.elapsed()).yellow()); + } Ok(()) }, Err(err) => { - println!("{}", "Failed!".red()); + if !json_ouput { + eprintln!("{}", "Failed!".red()); + } Err(err) }, }