From a382cf0e24cff5cfd5a0a26326e57a45fe701f2f Mon Sep 17 00:00:00 2001 From: Jiayu Ye Date: Tue, 13 Aug 2019 11:02:38 +0800 Subject: [PATCH] feat(protocol): Add the underlying data structure. (#5) * feat(protocol): Add the underlying data structure. * Update protocol/src/types/mod.rs Co-Authored-By: Wenchao Hu * Update protocol/src/types/transaction.rs Co-Authored-By: Wenchao Hu * cargo fmt. * vec -> Bytes * ensure len * asset id. * refactor: Remove the template code with derive_more. * update proposal. * add proof. * add validator. * typo * Change the receip.spender type. --- protocol/Cargo.toml | 3 + protocol/src/lib.rs | 22 +-- protocol/src/types/epoch.rs | 47 ++++++ protocol/src/types/mod.rs | 39 ++--- protocol/src/types/primitive.rs | 268 +++++++++++++++++++++++++++--- protocol/src/types/receipt.rs | 41 +++++ protocol/src/types/transaction.rs | 39 ++--- 7 files changed, 373 insertions(+), 86 deletions(-) create mode 100644 protocol/src/types/epoch.rs create mode 100644 protocol/src/types/receipt.rs diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 264c70f21..59a9a5ed9 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -7,7 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +derive_more = "0.15" async-trait = "0.1" +ethbloom = "0.6" bytes = "0.4" +sha3 = "0.8" uint = "0.8" hex = "0.3" diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 0ed1ac157..8a300da3a 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,12 +1,13 @@ #[macro_use] extern crate uint; +#[macro_use] +extern crate derive_more; pub mod codec; pub mod traits; pub mod types; use std::error::Error; -use std::fmt; #[derive(Debug, Clone)] pub enum ProtocolErrorKind { @@ -27,28 +28,13 @@ pub enum ProtocolErrorKind { } // refer to https://github.com/rust-lang/rust/blob/a17951c4f80eb5208030f91fdb4ae93919fa6b12/src/libstd/io/error.rs#L73 -#[derive(Debug)] +#[derive(Debug, Constructor, Display)] +#[display(fmt = "[ProtocolError] Kind: {:?} Error: {:?}", kind, error)] pub struct ProtocolError { kind: ProtocolErrorKind, error: Box, } -impl ProtocolError { - pub fn new(kind: ProtocolErrorKind, error: Box) -> Self { - Self { kind, error } - } -} - impl Error for ProtocolError {} -impl fmt::Display for ProtocolError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "[ProtocolError] Kind: {:?} Error: {:?}", - self.kind, self.error - ) - } -} - pub type ProtocolResult = Result; diff --git a/protocol/src/types/epoch.rs b/protocol/src/types/epoch.rs new file mode 100644 index 000000000..131d501ad --- /dev/null +++ b/protocol/src/types/epoch.rs @@ -0,0 +1,47 @@ +use bytes::Bytes; + +use crate::types::{AccountAddress, Bloom, Hash, MerkleRoot}; + +#[derive(Clone, Debug)] +pub struct Epoch { + pub header: EpochHeader, + pub ordered_tx_hashes: Vec, +} + +#[derive(Clone, Debug)] +pub struct EpochHeader { + pub chain_id: Hash, + pub epoch_id: u64, + pub pre_hash: Hash, + pub timestamp: u64, + pub logs_bloom: Bloom, + pub order_root: MerkleRoot, + pub confirm_root: Vec, + pub state_root: MerkleRoot, + pub receipt_root: Vec, + pub cycles_used: u64, + pub proposer: AccountAddress, + pub proof: Proof, + pub validator_version: u64, + pub validators: Option>, +} + +#[derive(Clone, Debug)] +pub struct Proof { + pub epoch_id: u64, + pub round: u64, + pub epoch_hash: Hash, + pub signature: Bytes, +} + +#[derive(Clone, Debug)] +pub struct Validator { + address: AccountAddress, + weight: u64, +} + +#[derive(Clone, Debug)] +pub struct Pill { + pub epoch: Epoch, + pub propose_hashes: Vec, +} diff --git a/protocol/src/types/mod.rs b/protocol/src/types/mod.rs index ab812c6d6..e5feeb008 100644 --- a/protocol/src/types/mod.rs +++ b/protocol/src/types/mod.rs @@ -1,41 +1,36 @@ +mod epoch; mod primitive; +mod receipt; mod transaction; use std::error::Error; -use std::fmt; use crate::{ProtocolError, ProtocolErrorKind}; -pub use primitive::{Address, Hash}; -pub use transaction::{ContractType, Fee, RawTransaction, SignedTransaction, TransactionAction}; +pub use epoch::{Epoch, EpochHeader, Pill, Proof, Validator}; +pub use ethbloom::{Bloom, BloomRef, Input as BloomInput}; +pub use primitive::{ + AccountAddress, AssetID, Balance, ContractAddress, ContractType, Fee, Hash, MerkleRoot, +}; +pub use receipt::{Receipt, ReceiptResult}; +pub use transaction::{RawTransaction, SignedTransaction, TransactionAction}; -#[derive(Debug)] +#[derive(Debug, Display, From)] pub enum TypesError { - HashLengthMismatch { expect: usize, real: usize }, + #[display(fmt = "Expect {:?}, get {:?}.", expect, real)] + LengthMismatch { expect: usize, real: usize }, + + #[display(fmt = "{:?}", error)] FromHex { error: hex::FromHexError }, + + #[display(fmt = "{:?} is an invalid address", address)] + InvalidAddress { address: String }, } impl Error for TypesError {} -impl fmt::Display for TypesError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let printable = match self { - TypesError::HashLengthMismatch { expect, real } => { - format!("Expect {:?} to get {:?}.", expect, real) - } - TypesError::FromHex { error } => format!("{:?}.", error), - }; - write!(f, "{}", printable) - } -} impl From for ProtocolError { fn from(error: TypesError) -> ProtocolError { ProtocolError::new(ProtocolErrorKind::Types, Box::new(error)) } } - -impl From for TypesError { - fn from(error: hex::FromHexError) -> Self { - TypesError::FromHex { error } - } -} diff --git a/protocol/src/types/primitive.rs b/protocol/src/types/primitive.rs index 36e34d434..749c97c43 100644 --- a/protocol/src/types/primitive.rs +++ b/protocol/src/types/primitive.rs @@ -3,53 +3,121 @@ construct_uint! { pub struct Balance(4); } +use std::fmt; + use bytes::Bytes; +use sha3::{Digest, Sha3_256}; use crate::types::TypesError; use crate::ProtocolResult; +/// Hash length const HASH_LEN: usize = 32; -const ADDRESS_LEN: usize = 21; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Hash([u8; HASH_LEN]); -#[derive(Clone, Debug)] -pub struct Address([u8; ADDRESS_LEN]); + +/// Merkel root hash +pub type MerkleRoot = Hash; +/// Asset ID +pub type AssetID = Hash; impl Hash { + /// Enter an array of bytes to get a 32-bit hash. + /// Note: sha3 is used for the time being and may be replaced with other + /// hashing algorithms later. + pub fn digest(bytes: Bytes) -> Self { + let mut out = [0u8; HASH_LEN]; + out.copy_from_slice(&Sha3_256::digest(&bytes)); + + Self(out) + } + + /// Converts the byte array to a Hash type. + /// Note: if you want to compute the hash value of the byte array, you + /// should call `fn digest`. pub fn from_bytes(bytes: Bytes) -> ProtocolResult { - if bytes.len() != ADDRESS_LEN { - return Err(TypesError::HashLengthMismatch { - expect: HASH_LEN, - real: bytes.len(), - } - .into()); - } + ensure_len(bytes.len(), HASH_LEN)?; let mut out = [0u8; HASH_LEN]; out.copy_from_slice(&bytes); - Ok(Self::from_fixed_bytes(out)) - } - - pub fn from_fixed_bytes(bytes: [u8; HASH_LEN]) -> Self { - Self(bytes) + Ok(Self(out)) } pub fn from_hex(s: &str) -> ProtocolResult { let s = clean_0x(s); let bytes = hex::decode(s).map_err(TypesError::from)?; - let mut out = [0u8; HASH_LEN]; - out.copy_from_slice(&bytes); - Ok(Self::from_fixed_bytes(out)) + let bytes = Bytes::from(bytes); + Self::from_bytes(bytes) } pub fn as_bytes(&self) -> Bytes { Bytes::from(self.0.as_ref()) } - pub fn into_fixed_bytes(self) -> [u8; HASH_LEN] { - self.0 + pub fn as_hex(&self) -> String { + hex::encode(self.0) + } +} + +impl fmt::Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_hex()) + } +} + +/// Address length. +const ADDRESS_LEN: usize = 21; +/// Magic number of account address. +const ACCOUNT_ADDRESS_MAGIC: u8 = 0x10; +/// Magic number of asset contract address. +const ASSET_CONTRACT_ADDRESS_MAGIC: u8 = 0x20; +/// Magic number of app contract address. +const APP_CONTRACT_ADDRESS_MAGIC: u8 = 0x21; +/// Magic number of library contract address. +const LIBRARY_CONTRACT_ADDRESS_MAGIC: u8 = 0x22; + +/// Contract type +#[derive(Clone, Debug)] +pub enum ContractType { + // Asset contract + Asset, + // App contract, the code in the contract is allowed to change the state world. + App, + // Library contract, the code in the contract is not allowed to change the state world. + Library, +} + +/// The address consists of 21 bytes, the first of which is a magic number that +/// identifies which type the address belongs to. +#[derive(Clone)] +struct Address([u8; ADDRESS_LEN]); + +/// Note: the account address here is an external account, not a contract. +#[derive(Clone, Debug)] +pub struct AccountAddress { + inner: Address, +} + +/// Contract address. +#[derive(Clone, Debug)] +pub struct ContractAddress { + inner: Address, + contract_type: ContractType, +} + +impl Address { + pub fn from_bytes(bytes: Bytes) -> ProtocolResult { + ensure_len(bytes.len(), ADDRESS_LEN)?; + + let mut out = [0u8; ADDRESS_LEN]; + out.copy_from_slice(&bytes); + Ok(Self(out)) + } + + pub fn as_bytes(&self) -> Bytes { + Bytes::from(self.0.as_ref()) } pub fn as_hex(&self) -> String { @@ -57,10 +125,166 @@ impl Hash { } } -pub fn clean_0x(s: &str) -> &str { +impl fmt::Debug for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.0.to_vec())) + } +} + +impl AccountAddress { + pub fn from_bytes(bytes: Bytes) -> ProtocolResult { + let magic = bytes.get(0).ok_or_else(|| TypesError::InvalidAddress { + address: hex::encode(bytes.to_vec()), + })?; + + if *magic != ACCOUNT_ADDRESS_MAGIC { + return Err(TypesError::InvalidAddress { + address: hex::encode(bytes.to_vec()), + } + .into()); + } + + let inner = Address::from_bytes(bytes)?; + Ok(AccountAddress { inner }) + } + + pub fn from_hex(s: &str) -> ProtocolResult { + let s = clean_0x(s); + let bytes = hex::decode(s).map_err(TypesError::from)?; + + let bytes = Bytes::from(bytes); + Self::from_bytes(bytes) + } + + pub fn as_hex(&self) -> String { + self.inner.as_hex() + } + + pub fn as_bytes(&self) -> Bytes { + self.inner.as_bytes() + } +} + +impl ContractAddress { + pub fn from_bytes(bytes: Bytes) -> ProtocolResult { + let magic = bytes.get(0).ok_or_else(|| TypesError::InvalidAddress { + address: hex::encode(bytes.to_vec()), + })?; + + let contract_type = match *magic { + ASSET_CONTRACT_ADDRESS_MAGIC => ContractType::Asset, + APP_CONTRACT_ADDRESS_MAGIC => ContractType::App, + LIBRARY_CONTRACT_ADDRESS_MAGIC => ContractType::Library, + _ => { + return Err(TypesError::InvalidAddress { + address: hex::encode(bytes.to_vec()), + } + .into()) + } + }; + + let inner = Address::from_bytes(bytes)?; + Ok(ContractAddress { + inner, + contract_type, + }) + } + + pub fn from_hex(s: &str) -> ProtocolResult { + let s = clean_0x(s); + let bytes = hex::decode(s).map_err(TypesError::from)?; + + let bytes = Bytes::from(bytes); + Self::from_bytes(bytes) + } + + pub fn as_hex(&self) -> String { + self.inner.as_hex() + } + + pub fn as_bytes(&self) -> Bytes { + self.inner.as_bytes() + } + + pub fn contract_type(&self) -> ContractType { + self.contract_type.clone() + } +} + +#[derive(Clone, Debug)] +pub struct Asset { + pub id: AssetID, + pub name: String, + pub symbol: String, + pub supply: Balance, + pub manage_contract: ContractAddress, + pub storage_root: MerkleRoot, +} + +#[derive(Clone, Debug)] +pub struct Fee { + pub asset_id: AssetID, + pub cycle: u64, +} + +fn clean_0x(s: &str) -> &str { if s.starts_with("0x") { &s[2..] } else { s } } + +fn ensure_len(real: usize, expect: usize) -> ProtocolResult<()> { + if real != expect { + Err(TypesError::LengthMismatch { expect, real }.into()) + } else { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use bytes::Bytes; + + use super::{AccountAddress, ContractAddress, Hash}; + + #[test] + fn test_hash() { + let hash = Hash::digest(Bytes::from("xxxxxx")); + + let bytes = hash.as_bytes(); + Hash::from_bytes(bytes).unwrap(); + } + + #[test] + fn test_address() { + // account address + let add_str = "10CAB8EEA4799C21379C20EF5BAA2CC8AF1BEC475B"; + let bytes = Bytes::from(hex::decode(add_str).unwrap()); + + let address = AccountAddress::from_bytes(bytes).unwrap(); + assert_eq!(add_str, address.as_hex().to_uppercase()); + + // asset contract address + let add_str = "20CAB8EEA4799C21379C20EF5BAA2CC8AF1BEC475B"; + let bytes = Bytes::from(hex::decode(add_str).unwrap()); + + let address = ContractAddress::from_bytes(bytes).unwrap(); + assert_eq!(add_str, address.as_hex().to_uppercase()); + + // app contract address + let add_str = "21CAB8EEA4799C21379C20EF5BAA2CC8AF1BEC475B"; + let bytes = Bytes::from(hex::decode(add_str).unwrap()); + + let address = ContractAddress::from_bytes(bytes).unwrap(); + assert_eq!(add_str, address.as_hex().to_uppercase()); + + // library contract address + let add_str = "22CAB8EEA4799C21379C20EF5BAA2CC8AF1BEC475B"; + let bytes = Bytes::from(hex::decode(add_str).unwrap()); + + let address = ContractAddress::from_bytes(bytes).unwrap(); + assert_eq!(add_str, address.as_hex().to_uppercase()); + } +} diff --git a/protocol/src/types/receipt.rs b/protocol/src/types/receipt.rs new file mode 100644 index 000000000..8d42ac72e --- /dev/null +++ b/protocol/src/types/receipt.rs @@ -0,0 +1,41 @@ +use bytes::Bytes; + +use crate::types::{ + AccountAddress, AssetID, Balance, Bloom, ContractAddress, ContractType, Fee, Hash, MerkleRoot, +}; + +#[derive(Clone, Debug)] +pub struct Receipt { + pub state_root: MerkleRoot, + pub epoch_id: u64, + pub tx_hash: Hash, + pub cycles_used: Fee, + pub result: ReceiptResult, +} + +#[derive(Clone, Debug)] +pub enum ReceiptResult { + Transfer { + receiver: AccountAddress, + before_amount: Balance, + after_amount: Balance, + }, + Approve { + spender: ContractAddress, + asset_id: AssetID, + max: Balance, + }, + Deploy { + contract: ContractAddress, + contract_type: ContractType, + }, + Call { + contract: ContractAddress, + return_value: Bytes, + logs_bloom: Box, + }, + Fail { + system: String, + user: String, + }, +} diff --git a/protocol/src/types/transaction.rs b/protocol/src/types/transaction.rs index 8e08a549e..965bca9e0 100644 --- a/protocol/src/types/transaction.rs +++ b/protocol/src/types/transaction.rs @@ -1,4 +1,8 @@ -use crate::types::primitive::{Address, Balance, Hash}; +use bytes::Bytes; + +use crate::types::primitive::{ + AccountAddress, AssetID, Balance, ContractAddress, ContractType, Fee, Hash, +}; #[derive(Clone, Debug)] pub struct RawTransaction { @@ -9,33 +13,27 @@ pub struct RawTransaction { pub action: TransactionAction, } -#[derive(Clone, Debug)] -pub struct Fee { - pub asset_id: Hash, - pub cycle: u64, -} - #[derive(Clone, Debug)] pub enum TransactionAction { Transfer { - receiver: Address, - asset_id: Hash, + receiver: AccountAddress, + asset_id: AssetID, amount: Balance, }, Approve { - spender: Address, - asset_id: Hash, + spender: ContractAddress, + asset_id: AssetID, max: Balance, }, Deploy { - code: Vec, + code: Bytes, contract_type: ContractType, }, Call { - contract: Address, + contract: ContractAddress, method: String, - args: Vec, - asset_id: Hash, + args: Vec, + asset_id: AssetID, amount: Balance, }, } @@ -44,13 +42,6 @@ pub enum TransactionAction { pub struct SignedTransaction { pub raw: RawTransaction, pub tx_hash: Hash, - pub pubkey: Vec, - pub signature: Vec, -} - -#[derive(Clone, Debug)] -pub enum ContractType { - Asset, - Library, - App, + pub pubkey: Bytes, + pub signature: Bytes, }