From c7833837368065c813e12308e87ddc1333332412 Mon Sep 17 00:00:00 2001 From: wenyuanhust Date: Wed, 1 Nov 2023 15:37:45 +0800 Subject: [PATCH] feat: move axon-tools to axon --- Cargo.lock | 36 +- Cargo.toml | 1 + devtools/axon-tools/Cargo.toml | 85 ++++ devtools/axon-tools/README.md | 3 + devtools/axon-tools/src/consts.rs | 16 + devtools/axon-tools/src/error.rs | 63 +++ devtools/axon-tools/src/hash.rs | 22 + devtools/axon-tools/src/hex.rs | 23 + devtools/axon-tools/src/lib.rs | 25 + devtools/axon-tools/src/proof.rs | 104 ++++ devtools/axon-tools/src/types.rs | 815 ++++++++++++++++++++++++++++++ 11 files changed, 1192 insertions(+), 1 deletion(-) create mode 100644 devtools/axon-tools/Cargo.toml create mode 100644 devtools/axon-tools/README.md create mode 100644 devtools/axon-tools/src/consts.rs create mode 100644 devtools/axon-tools/src/error.rs create mode 100644 devtools/axon-tools/src/hash.rs create mode 100644 devtools/axon-tools/src/hex.rs create mode 100644 devtools/axon-tools/src/lib.rs create mode 100644 devtools/axon-tools/src/proof.rs create mode 100644 devtools/axon-tools/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 935ab3e16..7e46262cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ dependencies = [ "async-trait", "bincode", "bytes", - "cita_trie", + "cita_trie 5.0.0", "ckb-hash", "ckb-jsonrpc-types", "ckb-sdk", @@ -335,6 +335,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "axon-tools" +version = "0.1.1" +dependencies = [ + "axon-protocol", + "bit-vec", + "blst", + "bytes", + "cita_trie 4.1.0", + "derive_more", + "ethereum", + "ethereum-types 0.14.1", + "faster-hex 0.8.1", + "log", + "overlord", + "rand 0.8.5", + "rlp", + "rlp-derive", + "serde", + "serde_json", + "tiny-keccak", +] + [[package]] name = "axum" version = "0.6.18" @@ -860,6 +883,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cita_trie" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c3d2abadaa28e0d277f9f6d07a2052544f045d929cd4d6f7bcfb43567c9767" +dependencies = [ + "hasher", + "parking_lot 0.12.1", + "rlp", +] + [[package]] name = "cita_trie" version = "5.0.0" diff --git a/Cargo.toml b/Cargo.toml index b4a9b6015..0448e3739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "core/run", "core/storage", "devtools/abi-generator", + "devtools/axon-tools", "devtools/keypair", "protocol", ] diff --git a/devtools/axon-tools/Cargo.toml b/devtools/axon-tools/Cargo.toml new file mode 100644 index 000000000..5bbc512d4 --- /dev/null +++ b/devtools/axon-tools/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "axon-tools" +version = "0.1.1" +edition = "2021" +authors = ["Axon Dev "] +license = "MIT" +include = ["src/*", "README.md", "LICENSE"] +readme = "README.md" +keywords = ["axon", "tool"] +categories = ["cryptography"] +repository = "https://github.com/axonweb3/axon" +description = """ +Some axon related utilities. +""" + +[dev-dependencies] +ethereum = "0.14" +rand = "0.8" + +[dependencies] +derive_more = "0.99" +log = "0.4.19" +overlord = "0.4" +protocol = { path = "../../protocol", package = "axon-protocol" } +serde_json = "1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies.bit-vec] +version = "0.6" +default_features = false +optional = true + +[dependencies.blst] +version = "0.3" +optional = true + +[dependencies.bytes] +version = "1.4" +default-features = false +features = ["serde"] + +[dependencies.cita_trie] +version = "4.0" +optional = true + +[dependencies.ethereum-types] +version = "0.14" +default-features = false +features = ["serialize"] + +[dependencies.faster-hex] +version = "0.8" +optional = true + +[dependencies.rlp] +version = "0.5" +default-features = false +optional = true + +[dependencies.rlp-derive] +version = "0.1" +optional = true + +[dependencies.serde] +version = "1.0" +default_features = false +optional = true +features = ["derive"] + +[dependencies.tiny-keccak] +version = "2.0" +optional = true +features = ["keccak"] + +[features] +default = ["impl-serde", "proof"] +proof = ["blst", "bit-vec", "cita_trie", "hash", "impl-rlp"] +hash = ["tiny-keccak"] +hex = ["faster-hex"] +impl-rlp = ["rlp", "rlp-derive", "ethereum-types/rlp"] +impl-serde = ["serde", "ethereum-types/serialize", "hex"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "doc_cfg"] diff --git a/devtools/axon-tools/README.md b/devtools/axon-tools/README.md new file mode 100644 index 000000000..2b236bbb9 --- /dev/null +++ b/devtools/axon-tools/README.md @@ -0,0 +1,3 @@ +This crate is used by forcerelay. +- Data structures like Block, Proposal etc. +- Block and Transaction verification APIs. \ No newline at end of file diff --git a/devtools/axon-tools/src/consts.rs b/devtools/axon-tools/src/consts.rs new file mode 100644 index 000000000..42d4dc1c0 --- /dev/null +++ b/devtools/axon-tools/src/consts.rs @@ -0,0 +1,16 @@ +use ethereum_types::H160; + +pub const METADATA_CONTRACT_ADDRESS: H160 = H160([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x01, +]); + +pub const CKB_LIGHT_CLIENT_CONTRACT_ADDRESS: H160 = H160([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x02, +]); + +pub const IMAGE_CELL_CONTRACT_ADDRESS: H160 = H160([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x03, +]); diff --git a/devtools/axon-tools/src/error.rs b/devtools/axon-tools/src/error.rs new file mode 100644 index 000000000..91134881c --- /dev/null +++ b/devtools/axon-tools/src/error.rs @@ -0,0 +1,63 @@ +use std::fmt::{self, Display}; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error { + InvalidProofBlockHash, + NotEnoughSignatures, + VerifyMptProof, + HexPrefix, + + #[cfg(feature = "hex")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "hex")))] + Hex(faster_hex::Error), + + #[cfg(feature = "proof")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] + Bls(blst::BLST_ERROR), + + #[cfg(feature = "proof")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] + Trie(cita_trie::TrieError), +} + +#[cfg(feature = "hex")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hex")))] +impl From for Error { + fn from(value: faster_hex::Error) -> Self { + Self::Hex(value) + } +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl From for Error { + fn from(e: blst::BLST_ERROR) -> Self { + Self::Bls(e) + } +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl From for Error { + fn from(e: cita_trie::TrieError) -> Self { + Self::Trie(e) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::InvalidProofBlockHash => write!(f, "Invalid proof block hash"), + Error::NotEnoughSignatures => write!(f, "Not enough signatures"), + Error::VerifyMptProof => write!(f, "Verify mpt proof"), + Error::HexPrefix => write!(f, "Hex prefix"), + #[cfg(feature = "hex")] + Error::Hex(e) => write!(f, "Hex error: {:?}", e), + #[cfg(feature = "proof")] + Error::Bls(e) => write!(f, "Bls error: {:?}", e), + #[cfg(feature = "proof")] + Error::Trie(e) => write!(f, "Trie error: {:?}", e), + } + } +} diff --git a/devtools/axon-tools/src/hash.rs b/devtools/axon-tools/src/hash.rs new file mode 100644 index 000000000..bc051bbe6 --- /dev/null +++ b/devtools/axon-tools/src/hash.rs @@ -0,0 +1,22 @@ +use tiny_keccak::{Hasher, Keccak}; + +#[cfg(feature = "hash")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hash")))] +pub fn keccak_256(data: &[u8]) -> [u8; 32] { + let mut ret = [0u8; 32]; + let mut hasher = Keccak::v256(); + hasher.update(data); + hasher.finalize(&mut ret); + ret +} + +#[derive(Default)] +pub(crate) struct InnerKeccak; + +impl cita_trie::Hasher for InnerKeccak { + const LENGTH: usize = 32; + + fn digest(&self, data: &[u8]) -> Vec { + keccak_256(data).to_vec() + } +} diff --git a/devtools/axon-tools/src/hex.rs b/devtools/axon-tools/src/hex.rs new file mode 100644 index 000000000..38caaebb6 --- /dev/null +++ b/devtools/axon-tools/src/hex.rs @@ -0,0 +1,23 @@ +use crate::Error; + +pub fn hex_encode>(src: T) -> String { + faster_hex::hex_string(src.as_ref()) +} + +pub fn hex_decode(src: &str) -> Result, Error> { + if src.is_empty() { + return Ok(Vec::new()); + } + + let src = if src.starts_with("0x") { + src.split_at(2).1 + } else { + src + }; + + let src = src.as_bytes(); + let mut ret = vec![0u8; src.len() / 2]; + faster_hex::hex_decode(src, &mut ret)?; + + Ok(ret) +} diff --git a/devtools/axon-tools/src/lib.rs b/devtools/axon-tools/src/lib.rs new file mode 100644 index 000000000..9037d7993 --- /dev/null +++ b/devtools/axon-tools/src/lib.rs @@ -0,0 +1,25 @@ +#![cfg_attr(doc_cfg, feature(doc_cfg))] + +extern crate alloc; + +mod error; +#[cfg(feature = "hash")] +pub mod hash; +#[cfg(feature = "hex")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hex")))] +pub mod hex; +#[cfg(feature = "proof")] +mod proof; +pub mod types; + +pub use error::Error; + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +pub use proof::{verify_proof, verify_trie_proof}; + +#[cfg(feature = "hash")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hash")))] +pub use hash::keccak_256; + +pub mod consts; diff --git a/devtools/axon-tools/src/proof.rs b/devtools/axon-tools/src/proof.rs new file mode 100644 index 000000000..9715d4426 --- /dev/null +++ b/devtools/axon-tools/src/proof.rs @@ -0,0 +1,104 @@ +use alloc::vec::Vec; + +use bit_vec::BitVec; +use blst::min_pk::{AggregatePublicKey, PublicKey, Signature}; +use blst::BLST_ERROR; +use bytes::Bytes; +use ethereum_types::H256; +use rlp::Encodable; + +use crate::types::{AxonBlock, Proof, Proposal, ValidatorExtend, Vote}; +use crate::{error::Error, hash::InnerKeccak, keccak_256}; + +const DST: &str = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RONUL"; + +pub fn verify_trie_proof( + root: H256, + key: &[u8], + proof: Vec>, +) -> Result>, Error> { + let value = cita_trie::verify_proof(&root.0, key, proof, InnerKeccak)?; + log::debug!("key: {:?}, value: {:?}", key, value); + Ok(value) +} + +pub fn verify_proof( + block: AxonBlock, + previous_state_root: H256, + validator_list: &mut [ValidatorExtend], + proof: Proof, +) -> Result<(), Error> { + let raw_proposal = Proposal { + version: block.header.version, + prev_hash: block.header.prev_hash, + proposer: block.header.proposer, + prev_state_root: previous_state_root, + transactions_root: block.header.transactions_root, + signed_txs_hash: block.header.signed_txs_hash, + timestamp: block.header.timestamp, + number: block.header.number, + gas_limit: block.header.gas_limit, + extra_data: block.header.extra_data, + base_fee_per_gas: block.header.base_fee_per_gas, + proof: block.header.proof, + chain_id: block.header.chain_id, + call_system_script_count: block.header.call_system_script_count, + tx_hashes: block.tx_hashes, + } + .rlp_bytes(); + + if keccak_256(&raw_proposal) != proof.block_hash.0 { + return Err(Error::InvalidProofBlockHash); + } + + let vote = Vote { + height: proof.number, + round: proof.round, + vote_type: 2u8, + block_hash: Bytes::from(proof.block_hash.0.to_vec()), + }; + + let hash_vote = keccak_256(rlp::encode(&vote).as_ref()); + let pks = extract_pks(&proof, validator_list)?; + let pks = pks.iter().collect::>(); + let c_pk = PublicKey::from_aggregate(&AggregatePublicKey::aggregate(&pks, true)?); + let sig = Signature::from_bytes(&proof.signature)?; + let res = sig.verify(true, &hash_vote, DST.as_bytes(), &[], &c_pk, true); + + if res == BLST_ERROR::BLST_SUCCESS { + return Ok(()); + } + + Err(res.into()) +} + +fn extract_pks( + proof: &Proof, + validator_list: &mut [ValidatorExtend], +) -> Result, Error> { + validator_list.sort(); + + let bit_map = BitVec::from_bytes(&proof.bitmap); + let mut pks = Vec::with_capacity(validator_list.len()); + let mut count = 0usize; + + for (v, bit) in validator_list.iter().zip(bit_map.iter()) { + if !bit { + continue; + } + + pks.push(PublicKey::from_bytes(&v.bls_pub_key.as_bytes())?); + count += 1; + } + + log::debug!( + "extract_pks count: {}, validator len: {}", + count, + validator_list.len() + ); + if count * 3 <= validator_list.len() * 2 { + return Err(Error::NotEnoughSignatures); + } + + Ok(pks) +} diff --git a/devtools/axon-tools/src/types.rs b/devtools/axon-tools/src/types.rs new file mode 100644 index 000000000..c26296865 --- /dev/null +++ b/devtools/axon-tools/src/types.rs @@ -0,0 +1,815 @@ +use core::cmp::Ordering; + +use alloc::vec::Vec; +use bytes::{Bytes, BytesMut}; +use core::str::FromStr; +use derive_more::{Display, From}; +use faster_hex::withpfx_lowercase; +use rlp::{Decodable, DecoderError, Rlp}; + +pub use ethereum_types::{Bloom, H160, H256, H64, U256}; + +#[cfg(feature = "impl-serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "impl-rlp")] +use rlp::{Encodable, RlpStream}; +use rlp_derive::{RlpDecodable, RlpEncodable}; + +#[cfg(feature = "hex")] +use crate::hex::{hex_decode, hex_encode}; +#[cfg(feature = "hex")] +use crate::Error; + +const HEX_PREFIX: &str = "0x"; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg(feature = "impl-rlp")] +#[derive(RlpEncodable, RlpDecodable)] +pub struct Hex(Bytes); + +impl Hex { + pub fn empty() -> Self { + Hex(Bytes::default()) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn encode>(src: T) -> Self { + Hex(BytesMut::from(src.as_ref()).freeze()) + } + + pub fn as_string(&self) -> String { + HEX_PREFIX.to_string() + &hex_encode(self.0.as_ref()) + } + + pub fn as_string_trim0x(&self) -> String { + hex_encode(self.0.as_ref()) + } + + pub fn as_bytes(&self) -> Bytes { + self.0.clone() + } + + fn is_prefixed(s: &str) -> bool { + s.starts_with(HEX_PREFIX) + } +} + +impl Default for Hex { + fn default() -> Self { + Hex(vec![0u8; 8].into()) + } +} + +impl AsRef<[u8]> for Hex { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl FromStr for Hex { + type Err = Error; + + fn from_str(s: &str) -> Result { + if !Self::is_prefixed(s) { + return Err(crate::Error::HexPrefix); + } + + Ok(Hex(hex_decode(&s[2..])?.into())) + } +} + +impl From for Bytes { + fn from(bytes: Hex) -> Self { + bytes.0 + } +} + +impl Serialize for Hex { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + withpfx_lowercase::serialize(&self.0, serializer) + } +} + +impl<'de> Deserialize<'de> for Hex { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + String::deserialize(deserializer) + .and_then(|s| Hex::from_str(&s).map_err(serde::de::Error::custom)) + } +} + +#[derive(Debug, Display, From)] +pub enum TypesError { + #[display(fmt = "Expect {:?}, get {:?}.", expect, real)] + LengthMismatch { expect: usize, real: usize }, + + #[display( + fmt = "Eip1559Transaction hash mismatch origin {:?}, computed {:?}", + origin, + calc + )] + TxHashMismatch { origin: H256, calc: H256 }, + + #[display(fmt = "{:?}", _0)] + FromHex(faster_hex::Error), + + #[display(fmt = "{:?} is an invalid address", _0)] + InvalidAddress(String), + + #[display(fmt = "Hex should start with 0x")] + HexPrefix, + + #[display(fmt = "Invalid public key")] + InvalidPublicKey, + + #[display(fmt = "Invalid check sum")] + InvalidCheckSum, + + #[display(fmt = "Unsigned")] + Unsigned, + + // #[display(fmt = "Crypto error {:?}", _0)] + // Crypto(CryptoError), + #[display(fmt = "Missing signature")] + MissingSignature, + + #[display(fmt = "Invalid crosschain direction")] + InvalidDirection, + + #[display(fmt = "Signature R is empty")] + SignatureRIsEmpty, + + #[display(fmt = "Invalid signature R type")] + InvalidSignatureRType, + + #[display(fmt = "Invalid address source type")] + InvalidAddressSourceType, + + #[display(fmt = "Missing interoperation sender")] + MissingInteroperationSender, + + #[display(fmt = "InvalidBlockVersion {:?}", _0)] + InvalidBlockVersion(u8), +} + +impl std::error::Error for TypesError {} + +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +#[cfg(feature = "impl-serde")] +#[derive(Serialize, Deserialize)] +pub enum BlockVersion { + #[default] + V0, +} + +impl From for u8 { + fn from(value: BlockVersion) -> Self { + match value { + BlockVersion::V0 => 0, + } + } +} + +impl TryFrom for BlockVersion { + type Error = TypesError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(BlockVersion::V0), + _ => Err(TypesError::InvalidBlockVersion(value)), + } + } +} + +impl Encodable for BlockVersion { + fn rlp_append(&self, s: &mut RlpStream) { + let ver: u8 = (*self).into(); + s.begin_list(1).append(&ver); + } +} + +impl Decodable for BlockVersion { + fn decode(r: &Rlp) -> Result { + let ver: u8 = r.val_at(0)?; + ver.try_into() + .map_err(|_| DecoderError::Custom("Invalid block version")) + } +} + +pub type Hash = H256; +pub type MerkleRoot = Hash; +pub type BlockNumber = u64; + +#[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ExtraData { + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "withpfx_lowercase::serialize", + deserialize_with = "withpfx_lowercase::deserialize" + ) + )] + pub inner: Bytes, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AxonHeader { + pub version: BlockVersion, + pub prev_hash: Hash, + pub proposer: H160, + pub state_root: MerkleRoot, + pub transactions_root: MerkleRoot, + pub signed_txs_hash: Hash, + pub receipts_root: MerkleRoot, + pub log_bloom: Bloom, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub timestamp: u64, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub number: BlockNumber, + pub gas_used: U256, + pub gas_limit: U256, + /// Extra data for the block header + /// The first index of extra_data is used to store hardfork information: + /// `HardforkInfoInner` + pub extra_data: Vec, + pub base_fee_per_gas: U256, + pub proof: Proof, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u32" + ) + )] + pub call_system_script_count: u32, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub chain_id: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AxonBlock { + pub header: AxonHeader, + pub tx_hashes: Vec, +} + +#[cfg(feature = "proof")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +#[cfg_attr(feature = "impl-serde", derive(serde::Deserialize))] +pub struct Proposal { + pub version: BlockVersion, + pub prev_hash: Hash, + pub proposer: H160, + pub prev_state_root: MerkleRoot, + pub transactions_root: MerkleRoot, + pub signed_txs_hash: Hash, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub timestamp: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub number: BlockNumber, + pub gas_limit: U256, + pub extra_data: Vec, + pub base_fee_per_gas: U256, + pub proof: Proof, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub chain_id: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u32") + )] + pub call_system_script_count: u32, + pub tx_hashes: Vec, +} + +#[cfg(feature = "impl-rlp")] +impl Encodable for Proposal { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(13) + .append(&self.version) + .append(&self.prev_hash) + .append(&self.proposer) + .append(&self.prev_state_root) + .append(&self.transactions_root) + .append(&self.signed_txs_hash) + .append(&self.timestamp) + .append(&self.number) + .append(&self.gas_limit.as_u64()) + .append_list(&self.extra_data) + .append(&self.proof) + .append(&self.call_system_script_count) + .append_list(&self.tx_hashes); + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Proof { + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub number: u64, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub round: u64, + pub block_hash: Hash, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "withpfx_lowercase::serialize", + deserialize_with = "withpfx_lowercase::deserialize" + ) + )] + pub signature: Bytes, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "withpfx_lowercase::serialize", + deserialize_with = "withpfx_lowercase::deserialize" + ) + )] + pub bitmap: Bytes, +} + +#[cfg(feature = "proof")] +#[derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Validator { + pub pub_key: Bytes, + pub propose_weight: u32, + pub vote_weight: u32, +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl core::cmp::PartialOrd for Validator { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl core::cmp::Ord for Validator { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.pub_key.cmp(&other.pub_key) + } +} + +#[cfg(feature = "proof")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Vote { + pub height: u64, + pub round: u64, + pub vote_type: u8, + pub block_hash: Bytes, +} + +#[cfg(feature = "impl-rlp")] +impl Encodable for Vote { + fn rlp_append(&self, s: &mut RlpStream) { + let vote_type: u8 = self.vote_type; + s.begin_list(4) + .append(&self.height) + .append(&self.round) + .append(&vote_type) + .append(&self.block_hash.to_vec()); + } +} + +#[cfg(test)] +impl Vote { + fn random() -> Self { + Self { + height: rand::random(), + round: rand::random(), + vote_type: 2, + block_hash: tests::random_bytes(32), + } + } +} + +#[derive(Default, Clone, Debug, Copy, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MetadataVersion { + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub start: BlockNumber, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub end: BlockNumber, +} + +impl MetadataVersion { + pub fn new(start: BlockNumber, end: BlockNumber) -> Self { + MetadataVersion { start, end } + } + + pub fn contains(&self, number: BlockNumber) -> bool { + self.start <= number && number <= self.end + } +} + +#[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Metadata { + pub version: MetadataVersion, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub epoch: u64, + pub verifier_list: Vec, + #[serde(skip_deserializing)] + pub propose_counter: Vec, + pub consensus_config: ConsensusConfig, +} + +#[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ConsensusConfig { + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub gas_limit: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub interval: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub propose_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub prevote_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub precommit_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub brake_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub tx_num_limit: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub max_tx_size: u64, +} + +#[derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ProposeCount { + pub address: H160, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub count: u64, +} + +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ValidatorExtend { + pub bls_pub_key: Hex, + pub pub_key: Hex, + pub address: H160, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u32") + )] + pub propose_weight: u32, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u32") + )] + pub vote_weight: u32, +} + +impl PartialOrd for ValidatorExtend { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ValidatorExtend { + fn cmp(&self, other: &Self) -> Ordering { + self.pub_key.as_bytes().cmp(&other.pub_key.as_bytes()) + } +} + +impl From for Validator { + fn from(ve: ValidatorExtend) -> Self { + Validator { + pub_key: ve.pub_key.as_bytes(), + propose_weight: ve.propose_weight, + vote_weight: ve.vote_weight, + } + } +} + +impl std::fmt::Debug for ValidatorExtend { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let bls_pub_key = self.bls_pub_key.as_string_trim0x(); + let pk = if bls_pub_key.len() > 8 { + unsafe { bls_pub_key.get_unchecked(0..8) } + } else { + bls_pub_key.as_str() + }; + + write!( + f, + "bls public key {:?}, public key {:?}, address {:?} propose weight {}, vote weight {}", + pk, self.pub_key, self.address, self.propose_weight, self.vote_weight + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NodePubKey { + pub bls_pub_key: Bytes, + pub pub_key: Bytes, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CkbRelatedInfo { + pub metadata_type_id: H256, + pub checkpoint_type_id: H256, + pub xudt_args: H256, + pub stake_smt_type_id: H256, + pub delegate_smt_type_id: H256, + pub reward_smt_type_id: H256, +} + +#[cfg(feature = "impl-serde")] +mod encode { + use ethereum_types::U256; + use serde::ser::Serializer; + static CHARS: &[u8] = b"0123456789abcdef"; + + fn to_hex_raw<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str { + debug_assert!(v.len() > 1 + bytes.len() * 2); + + v[0] = b'0'; + v[1] = b'x'; + + let mut idx = 2; + let first_nibble = bytes[0] >> 4; + if first_nibble != 0 || !skip_leading_zero { + v[idx] = CHARS[first_nibble as usize]; + idx += 1; + } + v[idx] = CHARS[(bytes[0] & 0xf) as usize]; + idx += 1; + + for &byte in bytes.iter().skip(1) { + v[idx] = CHARS[(byte >> 4) as usize]; + v[idx + 1] = CHARS[(byte & 0xf) as usize]; + idx += 2; + } + + // SAFETY: all characters come either from CHARS or "0x", therefore valid UTF8 + unsafe { std::str::from_utf8_unchecked(&v[0..idx]) } + } + + pub fn serialize_uint(val: &U, s: S) -> Result + where + S: Serializer, + U: Into + Copy, + { + let val: U256 = (*val).into(); + let mut slice = [0u8; 2 + 64]; + let mut bytes = [0u8; 32]; + val.to_big_endian(&mut bytes); + let non_zero = bytes.iter().take_while(|b| **b == 0).count(); + let bytes = &bytes[non_zero..]; + + if bytes.is_empty() { + s.serialize_str("0x0") + } else { + s.serialize_str(to_hex_raw(&mut slice, bytes, true)) + } + } +} + +#[cfg(feature = "impl-serde")] +mod decode { + use ethereum_types::U256; + use serde::de::{Deserialize, Deserializer}; + + pub fn from_hex(hex: &str) -> Result, &'static str> { + let mut bytes = Vec::with_capacity((hex.len() + 1) / 2); + + let mut start_i = 0; + if hex.len() % 2 != 0 { + let byte = + u8::from_str_radix(&hex[0..1], 16).map_err(|_| "Failed to parse hex string")?; + bytes.push(byte); + start_i = 1; + } + + for i in (start_i..hex.len()).step_by(2) { + let end_i = if i + 2 > hex.len() { i + 1 } else { i + 2 }; + let byte = + u8::from_str_radix(&hex[i..end_i], 16).map_err(|_| "Failed to parse hex string")?; + bytes.push(byte); + } + + Ok(bytes) + } + + pub fn deserialize_hex_u32<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s == "0x0" { + return Ok(0); + } + + if s.len() >= 2 && &s[0..2] == "0x" { + let bytes = from_hex(&s[2..]).map_err(serde::de::Error::custom)?; + let val = U256::from_big_endian(&bytes); + Ok(val.low_u32()) + } else { + Err(serde::de::Error::custom("Invalid format")) + } + } + + pub fn deserialize_hex_u64<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s == "0x0" { + return Ok(0); + } + + if s.len() >= 2 && &s[0..2] == "0x" { + let bytes = from_hex(&s[2..]).map_err(serde::de::Error::custom)?; + let val = U256::from_big_endian(&bytes); + Ok(val.low_u64()) + } else { + Err(serde::de::Error::custom("Invalid format")) + } + } + + #[cfg(test)] + mod tests { + #[cfg(all( + feature = "hex", + feature = "proof", + feature = "impl-serde", + feature = "impl-rlp" + ))] + #[test] + fn test_deserialize_hex_u64() { + use crate::types::MetadataVersion; + + { + let json_str = r#"{"start": "0x0", "end": "0x7"}"#; + let my_struct: MetadataVersion = serde_json::from_str(json_str).unwrap(); + assert_eq!(my_struct.start, 0x0); + assert_eq!(my_struct.end, 0x7); + } + + { + let json_str = r#"{"start": "0x12", "end": "0x233"}"#; + let my_struct: MetadataVersion = serde_json::from_str(json_str).unwrap(); + assert_eq!(my_struct.start, 0x12); + assert_eq!(my_struct.end, 0x233); + } + + { + let json_str = r#"{"start": "0x67fed12", "end": "0x8ddefa09"}"#; + let my_struct: MetadataVersion = serde_json::from_str(json_str).unwrap(); + assert_eq!(my_struct.start, 0x67fed12); + assert_eq!(my_struct.end, 0x8ddefa09); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub fn random_bytes(len: usize) -> Bytes { + (0..len).map(|_| rand::random()).collect::>().into() + } + + #[test] + fn test_vote_codec() { + let vote = Vote::random(); + let raw = rlp::encode(&vote); + let decoded: overlord::types::Vote = rlp::decode(&raw).unwrap(); + assert_eq!(vote.height, decoded.height); + assert_eq!(vote.round, decoded.round); + assert_eq!(vote.block_hash, decoded.block_hash); + } +}