From 46098cafed053735b31e96f9b03f24857ef945c4 Mon Sep 17 00:00:00 2001 From: Yin Guanhao <6764397+blckngm@users.noreply.github.com> Date: Mon, 18 Dec 2023 14:28:26 +0800 Subject: [PATCH] feat: cli for metadata cell data (#1640) --- Cargo.lock | 131 ++++++++++++-- Cargo.toml | 1 + devtools/metadata-cli/Cargo.toml | 20 +++ devtools/metadata-cli/README.md | 25 +++ devtools/metadata-cli/input.example.json | 66 +++++++ devtools/metadata-cli/input.example.toml | 24 +++ devtools/metadata-cli/src/main.rs | 190 +++++++++++++++++++++ devtools/metadata-cli/src/serde_helpers.rs | 71 ++++++++ 8 files changed, 516 insertions(+), 12 deletions(-) create mode 100644 devtools/metadata-cli/Cargo.toml create mode 100644 devtools/metadata-cli/README.md create mode 100644 devtools/metadata-cli/input.example.json create mode 100644 devtools/metadata-cli/input.example.toml create mode 100644 devtools/metadata-cli/src/main.rs create mode 100644 devtools/metadata-cli/src/serde_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 517a04298..2c9661e55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,6 +343,15 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "axon-types" +version = "0.1.0" +source = "git+https://github.com/axonweb3/axon-contract.git?rev=b82a843#b82a843b36a5565524a14a537a5dbe393e26ca65" +dependencies = [ + "molecule", + "molecule2", +] + [[package]] name = "axum" version = "0.6.18" @@ -849,6 +858,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits", + "serde", "time 0.1.45", "wasm-bindgen", "winapi", @@ -2248,8 +2258,18 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core 0.20.3", + "darling_macro 0.20.3", ] [[package]] @@ -2266,17 +2286,42 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.39", +] + [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core 0.20.3", + "quote", + "syn 2.0.39", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -2713,7 +2758,7 @@ name = "eth2_ssz_derive" version = "0.3.1" source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", "syn 1.0.109", @@ -4018,6 +4063,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -4028,6 +4074,7 @@ checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" dependencies = [ "equivalent", "hashbrown 0.14.0", + "serde", ] [[package]] @@ -4510,6 +4557,21 @@ dependencies = [ "safe_arith", ] +[[package]] +name = "metadata-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "axon-types", + "clap 4.4.6", + "hex", + "molecule", + "serde", + "serde_json", + "serde_with 3.4.0", + "toml 0.8.2", +] + [[package]] name = "metastruct" version = "0.1.1" @@ -4525,7 +4587,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37cb4045d5677b7da537f8cb5d0730d5b6414e3cc81c61e4b50e1f0cbdc73909" dependencies = [ - "darling", + "darling 0.13.4", "itertools", "proc-macro2", "quote", @@ -4596,6 +4658,11 @@ dependencies = [ "faster-hex 0.6.1", ] +[[package]] +name = "molecule2" +version = "0.1.0" +source = "git+https://github.com/axonweb3/axon-contract.git?rev=b82a843#b82a843b36a5565524a14a537a5dbe393e26ca65" + [[package]] name = "multimap" version = "0.8.3" @@ -6467,9 +6534,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -6513,7 +6580,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" dependencies = [ "serde", - "serde_with_macros", + "serde_with_macros 1.5.2", +] + +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.0", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.1", + "serde", + "serde_json", + "serde_with_macros 3.4.0", + "time 0.3.21", ] [[package]] @@ -6522,12 +6606,24 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling 0.20.3", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "serde_yaml" version = "0.8.26" @@ -6829,7 +6925,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b9e5728aa1a87141cefd4e7509903fc01fa0dcb108022b1e841a67c5159fc5" dependencies = [ - "darling", + "darling 0.13.4", "itertools", "proc-macro2", "quote", @@ -7181,8 +7277,10 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ + "itoa", "serde", "time-core", + "time-macros", ] [[package]] @@ -7191,6 +7289,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -7531,7 +7638,7 @@ name = "tree_hash_derive" version = "0.4.0" source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" dependencies = [ - "darling", + "darling 0.13.4", "quote", "syn 1.0.109", ] @@ -7624,7 +7731,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_with", + "serde_with 1.14.0", "serde_yaml", "slog", "smallvec", diff --git a/Cargo.toml b/Cargo.toml index 7e849071f..7df3c8d42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ "core/storage", "devtools/abi-generator", "devtools/axon-tools", + "devtools/metadata-cli", "protocol", ] diff --git a/devtools/metadata-cli/Cargo.toml b/devtools/metadata-cli/Cargo.toml new file mode 100644 index 000000000..b04a5a0b0 --- /dev/null +++ b/devtools/metadata-cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "metadata-cli" +version = "0.1.0" +edition = "2021" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "axon-metadata" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.75" +axon-types = { git = "https://github.com/axonweb3/axon-contract.git", rev = "b82a843" } +clap = { version = "4.4.6", features = ["derive"] } +hex = "0.4.3" +molecule = "0.7.5" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +serde_with = "3.4.0" +toml = "0.8.2" diff --git a/devtools/metadata-cli/README.md b/devtools/metadata-cli/README.md new file mode 100644 index 000000000..9a07a6e56 --- /dev/null +++ b/devtools/metadata-cli/README.md @@ -0,0 +1,25 @@ +# Metadata Cli + +Cli for working with axon metadata cell. + +## Get Data + +Generate `MetadataCellData` from validators. + +Usage: + +From a chain spec file or input file: + +```command +$ axon-metadata get-data -i input.example.toml +``` + +```command +$ axon-metadata get-data -i chain-spec.toml +``` + +From JSONRPC metadata result: + +```command +$ curl 'https://rpc-alphanet-axon.ckbapp.dev/' --header 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"axon_getCurrentMetadata","id":3}' | jq '.result' | axon-metadata get-data -i /dev/stdin +``` diff --git a/devtools/metadata-cli/input.example.json b/devtools/metadata-cli/input.example.json new file mode 100644 index 000000000..a9af6c241 --- /dev/null +++ b/devtools/metadata-cli/input.example.json @@ -0,0 +1,66 @@ +{ + "version": { + "start": "0x1", + "end": "0x5f5e100" + }, + "epoch": "0x0", + "verifier_list": [ + { + "bls_pub_key": "0x95a16ed1f4c43a7470917771bf820741dbd040c51967122de66dc5bc9f6eff5953a36be6c0fdf8c202a26d6f2b0f8885", + "pub_key": "0x035e184329714d7e3f7e39e5d96ac3be4835897adbaab85bae2558473391fa8fcf", + "address": "0x81fb11017e520fda13df35d1fd1a3903e384020f", + "propose_weight": "0x1", + "vote_weight": "0x1" + }, + { + "bls_pub_key": "0xa8d1c7c4152ce4ad8eff7ee90406b6cdf27eee97f0e520b8098a88ff3873c83aa8b74d9aab3a1c15361b5d3bc9224e9a", + "pub_key": "0x02f0018382fef706e37faf9770c14a58d35a457c7a2fa0ec84240d50b6d74f4adb", + "address": "0x7249d82017b77a5006808fe020e779af0d95e12e", + "propose_weight": "0x1", + "vote_weight": "0x1" + }, + { + "bls_pub_key": "0x8d999a5c29604f32950bfedf289f6b8e7e2f1a19f86b208d370024e709f77d1208f5e000dc4232a63064530613aa4b26", + "pub_key": "0x03ea501a5600c260b62ff0cdb0c227e9374643395243f026f9c73fe9aa8ebcfebd", + "address": "0x387f3253e85c320b5e881656447eedccf5230f22", + "propose_weight": "0x1", + "vote_weight": "0x1" + }, + { + "bls_pub_key": "0xafefcad3f6289c0bc0a9fd0015f533dcfcc1d7ba5161ff518702fee7aec33374a08d4fa45baeef85836c1e604e8f221d", + "pub_key": "0x02b2daf6d950f25735f2faa129cfe2a65c70bcbd4a050c9d18a06ed0e63a91f2a9", + "address": "0xc384b679bbc0de6e471fdb7735c0ff2d9508ece0", + "propose_weight": "0x1", + "vote_weight": "0x1" + } + ], + "propose_counter": [ + { + "address": "0x387f3253e85c320b5e881656447eedccf5230f22", + "count": "0xe605" + }, + { + "address": "0x7249d82017b77a5006808fe020e779af0d95e12e", + "count": "0xe605" + }, + { + "address": "0x81fb11017e520fda13df35d1fd1a3903e384020f", + "count": "0xe5f3" + }, + { + "address": "0xc384b679bbc0de6e471fdb7735c0ff2d9508ece0", + "count": "0xe5ff" + } + ], + "consensus_config": { + "gas_limit": "0x3e7fffffc18", + "interval": "0xbb8", + "propose_ratio": "0xf", + "prevote_ratio": "0xa", + "precommit_ratio": "0xa", + "brake_ratio": "0xa", + "tx_num_limit": "0x4e20", + "max_tx_size": "0x186a0000", + "max_contract_limit": "0xc000" + } +} diff --git a/devtools/metadata-cli/input.example.toml b/devtools/metadata-cli/input.example.toml new file mode 100644 index 000000000..004c87f65 --- /dev/null +++ b/devtools/metadata-cli/input.example.toml @@ -0,0 +1,24 @@ +[[verifier_list]] +bls_pub_key = "0xa26e3fe1cf51bd4822072c61bdc315ac32e3d3c2e2484bb92942666399e863b4bf56cf2926383cc706ffc15dfebc85c6" +pub_key = "0x031ddc35212b7fc7ff6685b17d91f77c972535aee5c7ae5684d3e72b986f08834b" +address = "0x8ab0cf264df99d83525e9e11c7e4db01558ae1b1" +propose_weight = "0x1" +vote_weight = 1 + +[[verifier_list]] +bls_pub_key = "0x80310fa9df724b5603d283b472ed3bf85254a8a4ceda8a274b421f6cf2be1d9184267cdfe9a199d36ff14e57668a55d0" +pub_key = "0x02b77c74eb68af3d4d6cc7884ed6709f1a2a1af0f713382a4438ec2ea3a70d4d7f" +address = "0xf386573563c3a75dbbd269fce9782620826ddac2" +propose_weight = 1 +vote_weight = 1 + +[[verifier_list]] +bls_pub_key = "0x897721e9016864141a8b982a48217f66ef318ce598aa31842cddaaebe3cd7feab17050022afa6c2123aba39938fe4142" +pub_key = "0x027ffd6a6a231561f2afe5878b1c743323b34263d16787130b1815fe35649b0bf5" +address = "0x8af204ac5d7cb8815a6c53a50b72d01e729d3b22" +propose_weight = 1 +vote_weight = "0x01" + +[[verifier_list]] +bls_pub_key = "0x98eef09a3927acb225191101a1d9aa85775fdcdc87b9ba36898f6c132b485d66aef91c0f51cda331be4f985c3be6761c" +pub_key = "0x0232c489c23b1207107e9a24648c1e4754a8c1c0b38db96df57a526201035058cb" diff --git a/devtools/metadata-cli/src/main.rs b/devtools/metadata-cli/src/main.rs new file mode 100644 index 000000000..e13bac304 --- /dev/null +++ b/devtools/metadata-cli/src/main.rs @@ -0,0 +1,190 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use axon_types::{ + basic::{Byte33, Byte48, Identity, Uint32, Uint64}, + metadata::{Metadata, MetadataCellData, MetadataList, ValidatorList}, +}; +use clap::{Parser, Subcommand}; +use molecule::prelude::{Builder, Entity}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, PickFirst}; + +mod serde_helpers; +use serde_helpers::{HexBytes, HexU32, HexU64}; + +#[serde_as] +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Validator { + #[serde_as(as = "HexBytes")] + pub bls_pub_key: [u8; 48], + + #[serde_as(as = "HexBytes")] + pub pub_key: [u8; 33], + + #[serde_as(as = "HexBytes")] + #[serde(default)] + pub address: [u8; 20], + + #[serde_as(deserialize_as = "PickFirst<(_, HexU64)>")] + #[serde(default)] + pub propose_count: u64, + + #[serde_as(deserialize_as = "PickFirst<(_, HexU32)>")] + #[serde(default)] + pub propose_weight: u32, + + #[serde_as(deserialize_as = "PickFirst<(_, HexU32)>")] + #[serde(default)] + pub vote_weight: u32, +} + +impl From for axon_types::metadata::Validator { + fn from(value: Validator) -> Self { + Self::new_builder() + .bls_pub_key(Byte48::from_slice(&value.bls_pub_key).unwrap()) + .pub_key(Byte33::from_slice(&value.pub_key).unwrap()) + .address(Identity::from_slice(&value.address).unwrap()) + .propose_count(Uint64::from_slice(&value.propose_count.to_le_bytes()).unwrap()) + .propose_weight(Uint32::from_slice(&value.propose_weight.to_le_bytes()).unwrap()) + .vote_weight(Uint32::from_slice(&value.vote_weight.to_le_bytes()).unwrap()) + .build() + } +} + +pub fn metadata_cell_data_with_validators(vs: ValidatorList) -> MetadataCellData { + MetadataCellData::new_builder() + .metadata( + MetadataList::new_builder() + .push(Metadata::new_builder().validators(vs).build()) + .build(), + ) + .build() +} + +#[derive(Deserialize, Serialize)] +pub struct Input { + #[serde(alias = "verifier_list")] + pub validators: Vec, +} + +#[derive(Deserialize, Serialize)] +pub struct Spec { + params: Input, +} + +#[derive(Parser)] +struct Cli { + #[clap(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + GetData(GetData), +} + +impl Command { + fn run(self) -> Result<()> { + match self { + Command::GetData(g) => g.run(), + } + } +} + +#[derive(Parser)] +struct GetData { + #[arg(short, long)] + input: PathBuf, + #[arg(short, long)] + output: Option, + #[arg(long)] + binary: bool, +} + +impl GetData { + fn run(self) -> Result<()> { + let input = std::fs::read_to_string(self.input).context("read input file")?; + let mut output: Box = if let Some(o) = self.output { + Box::new( + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(o) + .context("open output file")?, + ) + } else { + Box::new(std::io::stdout()) + }; + + let input: Input = if input.trim_start().starts_with('{') { + serde_json::from_str(&input).context("parsing input")? + } else { + let spec_or_input: toml::Value = toml::from_str(&input).context("parsing input")?; + if spec_or_input.get("params").is_some_and(|v| v.is_table()) { + toml::from_str::(&input) + .context("parsing input")? + .params + } else { + toml::from_str::(&input).context("parsing input")? + } + }; + let mut vs = ValidatorList::new_builder(); + for v in input.validators { + vs = vs.push(v.into()); + } + let md = metadata_cell_data_with_validators(vs.build()); + if self.binary { + output.write_all(md.as_slice()).context("writing output")?; + } else { + writeln!(output, "0x{}", hex::encode(md.as_slice())).context("writing output")?; + } + Ok(()) + } +} + +fn main() -> Result<()> { + Cli::parse().command.run() +} + +#[cfg(test)] +mod tests { + use anyhow::{Context, Result}; + use clap::Parser; + + use super::GetData; + + #[test] + fn test_get_data() -> Result<()> { + GetData::parse_from([ + "get-data", + "-i", + concat!(env!("CARGO_MANIFEST_DIR"), "/input.example.toml",), + ]) + .run() + .context("test toml")?; + + GetData::parse_from([ + "get-data", + "-i", + concat!(env!("CARGO_MANIFEST_DIR"), "/input.example.json",), + ]) + .run() + .context("test json")?; + + GetData::parse_from([ + "get-data", + "-i", + concat!( + env!("CARGO_MANIFEST_DIR"), + "/../chain/specs/multi_nodes/chain-spec.toml", + ), + ]) + .run() + .context("test spec")?; + + Ok(()) + } +} diff --git a/devtools/metadata-cli/src/serde_helpers.rs b/devtools/metadata-cli/src/serde_helpers.rs new file mode 100644 index 000000000..fc4f732f3 --- /dev/null +++ b/devtools/metadata-cli/src/serde_helpers.rs @@ -0,0 +1,71 @@ +use serde::{de::Error, Deserialize}; +use serde_with::{DeserializeAs, SerializeAs}; + +pub struct HexBytes; + +impl SerializeAs for HexBytes +where + T: AsRef<[u8]>, +{ + fn serialize_as(source: &T, serializer: S) -> Result + where + S: serde::Serializer, + { + let source = source.as_ref(); + let mut ret = vec![0; 2 + source.len() * 2]; + ret[..2].copy_from_slice(b"0x"); + hex::encode_to_slice(source, &mut ret[2..]).map_err(serde::ser::Error::custom)?; + + serializer.serialize_str(unsafe { std::str::from_utf8_unchecked(&ret) }) + } +} + +impl<'de, T> DeserializeAs<'de, T> for HexBytes +where + T: TryFrom>, +{ + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + hex::decode(s.strip_prefix("0x").unwrap_or(&s)) + .map_err(Error::custom)? + .try_into() + .map_err(|_e| { + Error::custom("failed to convert from vector, incorrect length?".to_string()) + }) + } +} + +pub struct HexU32; + +impl<'de> DeserializeAs<'de, u32> for HexU32 { + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let s = s + .strip_prefix("0x") + .ok_or_else(|| Error::custom("expect string that starts with `0x`"))?; + let v = u32::from_str_radix(s, 16).map_err(Error::custom)?; + Ok(v) + } +} + +pub struct HexU64; + +impl<'de> DeserializeAs<'de, u64> for HexU64 { + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let s = s + .strip_prefix("0x") + .ok_or_else(|| Error::custom("expect string that starts with `0x`"))?; + let v = u64::from_str_radix(s, 16).map_err(Error::custom)?; + Ok(v) + } +}