diff --git a/Cargo.toml b/Cargo.toml index 215febca3..e3cb00264 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "light-client", "light-node", "proto", - "proto-derive", "rpc", "tendermint", "testgen" diff --git a/proto-derive/Cargo.toml b/proto-derive/Cargo.toml deleted file mode 100644 index a62629ee9..000000000 --- a/proto-derive/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "tendermint-proto-derive" -version = "0.1.0" -authors = ["Greg Szabo "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = "1.0" diff --git a/proto-derive/src/lib.rs b/proto-derive/src/lib.rs deleted file mode 100644 index d1ec31bde..000000000 --- a/proto-derive/src/lib.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! The DomainType derive macro implements the tendermint_proto::DomainType trait. -//! This implementation uses the Prost library to convert between Raw types and byte streams. -//! -//! Read more about how to use this macro in the DomainType trait definition. - -use proc_macro::TokenStream; -use quote::quote; -use syn::spanned::Spanned; - -#[proc_macro_derive(DomainType, attributes(rawtype))] -pub fn domaintype(input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input as syn::DeriveInput); - expand_domaintype(&input) -} - -fn expand_domaintype(input: &syn::DeriveInput) -> TokenStream { - let ident = &input.ident; - - // Todo: Make this function more robust and easier to read. - - let rawtype_attributes = &input - .attrs - .iter() - .filter(|&attr| attr.path.is_ident("rawtype")) - .collect::>(); - if rawtype_attributes.len() != 1 { - return syn::Error::new( - rawtype_attributes.first().span(), - "exactly one #[rawtype(RawType)] expected", - ) - .to_compile_error() - .into(); - } - - let rawtype_tokens = rawtype_attributes[0] - .tokens - .clone() - .into_iter() - .collect::>(); - if rawtype_tokens.len() != 1 { - return syn::Error::new(rawtype_attributes[0].span(), "#[rawtype(RawType)] expected") - .to_compile_error() - .into(); - } - - let rawtype = match &rawtype_tokens[0] { - proc_macro2::TokenTree::Group(group) => group.stream(), - _ => { - return syn::Error::new( - rawtype_tokens[0].span(), - "#[rawtype(RawType)] group expected", - ) - .to_compile_error() - .into() - } - }; - - let gen = quote! { - impl ::tendermint_proto::DomainType<#rawtype> for #ident { - - fn encode(self, buf: &mut B) -> ::std::result::Result<(), ::tendermint_proto::Error> { - use ::tendermint_proto::prost::Message; - #rawtype::from(self).encode(buf).map_err(|e| ::tendermint_proto::Kind::EncodeMessage.context(e).into()) - } - - fn encode_length_delimited(self, buf: &mut B) -> ::std::result::Result<(), ::tendermint_proto::Error> { - use ::tendermint_proto::prost::Message; - #rawtype::from(self).encode_length_delimited(buf).map_err(|e| ::tendermint_proto::Kind::EncodeMessage.context(e).into()) - } - - fn decode(buf: B) -> Result { - use ::tendermint_proto::prost::Message; - #rawtype::decode(buf).map_or_else( - |e| ::std::result::Result::Err(::tendermint_proto::Kind::DecodeMessage.context(e).into()), - |t| Self::try_from(t).map_err(|e| ::tendermint_proto::Kind::TryIntoDomainType.context(e).into()) - ) - } - - fn decode_length_delimited(buf: B) -> Result { - use ::tendermint_proto::prost::Message; - #rawtype::decode_length_delimited(buf).map_or_else( - |e| ::std::result::Result::Err(::tendermint_proto::Kind::DecodeMessage.context(e).into()), - |t| Self::try_from(t).map_err(|e| ::tendermint_proto::Kind::TryIntoDomainType.context(e).into()) - ) - } - - fn encoded_len(self) -> usize { - use ::tendermint_proto::prost::Message; - #rawtype::from(self).encoded_len() - } - - } - }; - gen.into() -} diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 72ff2ff2d..77776b8a8 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -16,13 +16,9 @@ description = """ [package.metadata.docs.rs] all-features = true -[features] -default = ["tendermint-proto-derive"] - [dependencies] prost = { version = "0.6" } prost-types = { version = "0.6" } -tendermint-proto-derive = { path = "../proto-derive", optional = true } bytes = "0.5" anomaly = "0.2" thiserror = "1.0" diff --git a/proto/src/domaintype.rs b/proto/src/domaintype.rs index b595dad42..59c9868e0 100644 --- a/proto/src/domaintype.rs +++ b/proto/src/domaintype.rs @@ -9,8 +9,8 @@ //! The benefits include decoding the wire into a struct that is inherently valid as well as hiding //! the encoding and decoding details from the developer. This latter is important if/when we decide //! to exchange the underlying Prost library with something else. (Another protobuf implementation -//! or a completely different encoding.) Encoding is not the core product of Tendermint it's a -//! necessary dependency. +//! or a completely different encoding.) Encoding is not the core product it's a necessary +//! dependency. //! //! //! Decode: bytestream -> Raw -> Domain @@ -33,47 +33,107 @@ //! Note that the Prost library and the TryFrom method have their own set of errors. These are //! merged into a custom Error type defined in this crate for easier handling. //! +//! Requirements: +//! * The DomainType trait requires the struct to implement the Clone trait. +//! * Any RawType structure implements the prost::Message trait. (protobuf struct) +//! * The DomainType trait requires that the TryFrom implemented on the structure has an +//! error type that implements Into. (The current implementations with anomaly are +//! fine.) //! //! How to implement a DomainType struct: //! 1. Implement your struct based on your expectations for the developer -//! 2. Add the derive macro `#[derive(DomainType)]` on top of it -//! 3. Add the Raw type as a parameter of the DomainType trait (`[rawtype(MyRawType)]`) +//! 2. Add `impl DomainType for MyDomainType {}` blanket implementation of the trait //! 4. Implement the `TryFrom for MyDomainType` trait //! 5. Implement the `From for MyRawType` trait -//! -//! Note: the `[rawtype()]` parameter is similar to how `serde` implements serialization through a -//! `[serde(with="")]` interim type. -//! -use crate::Error; +use crate::{Error, Kind}; +use anomaly::BoxError; use bytes::{Buf, BufMut}; -use prost::Message; +use prost::{encoding::encoded_len_varint, Message}; +use std::convert::{TryFrom, TryInto}; /// DomainType trait allows protobuf encoding and decoding for domain types -pub trait DomainType>: Sized { +pub trait DomainType + Default> +where + Self: Sized + Clone + TryFrom, + >::Error: Into, +{ /// Encodes the DomainType into a buffer. /// - /// The DomainType will be consumed. - fn encode(self, buf: &mut B) -> Result<(), Error>; + /// This function replaces the Prost::Message encode() function for DomainTypes. + fn encode(&self, buf: &mut B) -> Result<(), Error> { + T::from(self.clone()) + .encode(buf) + .map_err(|e| Kind::EncodeMessage.context(e).into()) + } /// Encodes the DomainType with a length-delimiter to a buffer. /// - /// The DomainType will be consumed. /// An error will be returned if the buffer does not have sufficient capacity. - fn encode_length_delimited(self, buf: &mut B) -> Result<(), Error>; + /// + /// This function replaces the Prost::Message encode_length_delimited() function for + /// DomainTypes. + fn encode_length_delimited(&self, buf: &mut B) -> Result<(), Error> { + T::from(self.clone()) + .encode_length_delimited(buf) + .map_err(|e| Kind::EncodeMessage.context(e).into()) + } /// Decodes an instance of the message from a buffer and then converts it into DomainType. /// /// The entire buffer will be consumed. - fn decode(buf: B) -> Result; + /// + /// This function replaces the Prost::Message decode() function for DomainTypes. + fn decode(buf: B) -> Result { + T::decode(buf).map_or_else( + |e| Err(Kind::DecodeMessage.context(e).into()), + |t| Self::try_from(t).map_err(|e| Kind::TryIntoDomainType.context(e).into()), + ) + } /// Decodes a length-delimited instance of the message from the buffer. /// /// The entire buffer will be consumed. - fn decode_length_delimited(buf: B) -> Result; + /// + /// This function replaces the Prost::Message decode_length_delimited() function for + /// DomainTypes. + fn decode_length_delimited(buf: B) -> Result { + T::decode_length_delimited(buf).map_or_else( + |e| Err(Kind::DecodeMessage.context(e).into()), + |t| Self::try_from(t).map_err(|e| Kind::TryIntoDomainType.context(e).into()), + ) + } /// Returns the encoded length of the message without a length delimiter. /// - /// The DomainType will be consumed. - fn encoded_len(self) -> usize; + /// This function replaces the Prost::Message encoded_len() function for DomainTypes. + fn encoded_len(&self) -> usize { + T::from(self.clone()).encoded_len() + } + + /// Encodes the DomainType into a protobuf-encoded Vec + fn encode_vec(&self) -> Result, Error> { + let mut wire = Vec::with_capacity(self.encoded_len()); + self.encode(&mut wire).map(|_| wire) + } + + /// Decodes a protobuf-encoded instance of the message from a Vec and then converts it into + /// DomainType. + fn decode_vec(v: &[u8]) -> Result { + Self::decode(v) + } + + /// Encodes the DomainType with a length-delimiter to a Vec protobuf-encoded message. + fn encode_length_delimited_vec(&self) -> Result, Error> { + let len = self.encoded_len(); + let lenu64 = len.try_into().map_err(|e| Kind::EncodeMessage.context(e))?; + let mut wire = Vec::with_capacity(len + encoded_len_varint(lenu64)); + self.encode_length_delimited(&mut wire).map(|_| wire) + } + + /// Decodes a protobuf-encoded instance of the message with a length-delimiter from a Vec + /// and then converts it into DomainType. + fn decode_length_delimited_vec(v: &[u8]) -> Result { + Self::decode_length_delimited(v) + } } diff --git a/proto/src/lib.rs b/proto/src/lib.rs index f223cf4b8..831cd8492 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -93,12 +93,6 @@ pub use domaintype::DomainType; mod error; pub use error::{Error, Kind}; -// Re-export the bytes and prost crates for use within derived code. -#[doc(hidden)] -pub use bytes; -#[doc(hidden)] -pub use prost; - // Re-export the DomainType derive macro #[derive(DomainType)] #[cfg(feature = "tendermint-proto-derive")] #[doc(hidden)] diff --git a/proto/tests/unit.rs b/proto/tests/unit.rs index 2dbc58b69..95c878dc3 100644 --- a/proto/tests/unit.rs +++ b/proto/tests/unit.rs @@ -3,9 +3,10 @@ use tendermint_proto::types::BlockId as RawBlockId; use tendermint_proto::types::PartSetHeader as RawPartSetHeader; use tendermint_proto::DomainType; +impl DomainType for BlockId {} + // Example implementation of a protobuf struct using DomainType. -#[derive(DomainType, Clone)] -#[rawtype(RawBlockId)] +#[derive(Clone, Debug)] pub struct BlockId { hash: String, part_set_header_exists: bool, @@ -40,6 +41,13 @@ impl From for RawBlockId { } } +// Do any custom implementation for your type +impl PartialEq for BlockId { + fn eq(&self, other: &Self) -> bool { + self.part_set_header_exists == other.part_set_header_exists && self.hash == other.hash + } +} + #[test] pub fn domaintype_struct_example() { let my_domain_type = BlockId { @@ -48,7 +56,7 @@ pub fn domaintype_struct_example() { }; let mut wire = vec![]; - my_domain_type.clone().encode(&mut wire).unwrap(); + my_domain_type.encode(&mut wire).unwrap(); assert_eq!( wire, vec![10, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] @@ -77,3 +85,27 @@ pub fn domaintype_struct_length_delimited_example() { assert_eq!(new_domain_type.hash, "Hello world!".to_string()); assert_eq!(new_domain_type.part_set_header_exists, false); } + +#[test] +pub fn domaintype_struct_conveniences_example() { + let my_domain_type = BlockId { + hash: "Hello world!".to_string(), + part_set_header_exists: false, + }; + + let wire = my_domain_type.encode_vec().unwrap(); + assert_eq!( + wire, + vec![10, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] + ); + let new_domain_type = BlockId::decode_vec(&wire).unwrap(); + assert_eq!(my_domain_type, new_domain_type); + + let wire = my_domain_type.encode_length_delimited_vec().unwrap(); + assert_eq!( + wire, + vec![14, 10, 12, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] + ); + let new_domain_type = BlockId::decode_length_delimited_vec(&wire).unwrap(); + assert_eq!(my_domain_type, new_domain_type); +} diff --git a/tendermint/src/amino_types/block_id.rs b/tendermint/src/amino_types/block_id.rs index 8708710d1..f70b41400 100644 --- a/tendermint/src/amino_types/block_id.rs +++ b/tendermint/src/amino_types/block_id.rs @@ -13,9 +13,10 @@ use tendermint_proto::types::CanonicalPartSetHeader as RawCanonicalPartSetHeader use tendermint_proto::types::PartSetHeader as RawPartSetHeader; use tendermint_proto::DomainType; +impl DomainType for BlockId {} + /// BlockID -#[derive(Clone, PartialEq, Debug, DomainType)] -#[rawtype(RawBlockId)] +#[derive(Clone, PartialEq, Debug)] pub struct BlockId { pub hash: Vec, pub part_set_header: ::std::option::Option, @@ -89,8 +90,9 @@ impl ConsensusMessage for BlockId { } } -#[derive(Clone, PartialEq, DomainType)] -#[rawtype(RawCanonicalBlockId)] +impl DomainType for CanonicalBlockId {} + +#[derive(Clone, PartialEq)] pub struct CanonicalBlockId { pub hash: Vec, pub part_set_header: Option, @@ -124,6 +126,8 @@ impl From for RawCanonicalBlockId { } } +impl DomainType for PartSetHeader {} + impl block::ParseId for CanonicalBlockId { fn parse_block_id(&self) -> Result { let hash = Hash::new(hash::Algorithm::Sha256, &self.hash)?; @@ -134,10 +138,8 @@ impl block::ParseId for CanonicalBlockId { Ok(block::Id::new(hash, part_set_header)) } } - /// PartsetHeader -#[derive(Clone, PartialEq, Debug, DomainType)] -#[rawtype(RawPartSetHeader)] +#[derive(Clone, PartialEq, Debug)] pub struct PartSetHeader { pub total: i64, pub hash: Vec, @@ -193,8 +195,8 @@ impl ConsensusMessage for PartSetHeader { } } -#[derive(Clone, PartialEq, DomainType)] -#[rawtype(RawCanonicalPartSetHeader)] +impl DomainType for CanonicalPartSetHeader {} +#[derive(Clone, PartialEq)] pub struct CanonicalPartSetHeader { pub total: u32, pub hash: Vec, diff --git a/tendermint/src/amino_types/ed25519.rs b/tendermint/src/amino_types/ed25519.rs index 91253af9e..00c04b7a9 100644 --- a/tendermint/src/amino_types/ed25519.rs +++ b/tendermint/src/amino_types/ed25519.rs @@ -16,9 +16,9 @@ use tendermint_proto::DomainType; // version. // TODO(ismail): make this more generic (by modifying prost and adding a trait for PubKey) +impl DomainType for PubKeyResponse {} /// PubKeyResponse is a response message containing the public key. -#[derive(Clone, PartialEq, Debug, DomainType)] -#[rawtype(RawPubKeyResponse)] +#[derive(Clone, PartialEq, Debug)] pub struct PubKeyResponse { pub pub_key: Option, pub error: Option, @@ -44,9 +44,9 @@ impl From for RawPubKeyResponse { } } +impl DomainType for PubKeyRequest {} /// PubKeyRequest requests the consensus public key from the remote signer. -#[derive(Clone, PartialEq, Debug, DomainType)] -#[rawtype(RawPubKeyRequest)] +#[derive(Clone, PartialEq, Debug)] pub struct PubKeyRequest { pub chain_id: String, } @@ -135,7 +135,7 @@ mod tests { chain_id: "".to_string(), }; let mut got = vec![]; - let _have = msg.clone().encode(&mut got); + let _have = msg.encode(&mut got); assert_eq!(got, want); @@ -190,7 +190,7 @@ mod tests { error: None, }; let mut got = vec![]; - let _have = msg.clone().encode(&mut got); + let _have = msg.encode(&mut got); assert_eq!(got, encoded); diff --git a/tendermint/src/amino_types/proposal.rs b/tendermint/src/amino_types/proposal.rs index cf766d8e4..2e1106faa 100644 --- a/tendermint/src/amino_types/proposal.rs +++ b/tendermint/src/amino_types/proposal.rs @@ -22,8 +22,8 @@ use tendermint_proto::types::Proposal as RawProposal; use tendermint_proto::types::SignedMsgType; use tendermint_proto::DomainType; -#[derive(Clone, PartialEq, Debug, DomainType)] -#[rawtype(RawProposal)] +impl DomainType for Proposal {} +#[derive(Clone, PartialEq, Debug)] pub struct Proposal { pub msg_type: u16, pub height: u32, @@ -103,9 +103,9 @@ impl block::ParseHeight for Proposal { } } +impl DomainType for SignProposalRequest {} /// SignProposalRequest is a request to sign a proposal -#[derive(Clone, PartialEq, Debug, DomainType)] -#[rawtype(RawSignProposalRequest)] +#[derive(Clone, PartialEq, Debug)] pub struct SignProposalRequest { pub proposal: Option, pub chain_id: String, @@ -137,8 +137,9 @@ impl From for RawSignProposalRequest { } } -#[derive(Clone, PartialEq, DomainType)] -#[rawtype(RawCanonicalProposal)] +impl DomainType for CanonicalProposal {} + +#[derive(Clone, PartialEq)] pub struct CanonicalProposal { /// type alias for byte pub msg_type: u16, @@ -225,9 +226,10 @@ impl block::ParseHeight for CanonicalProposal { } } +impl DomainType for SignedProposalResponse {} + /// SignedProposalResponse is response containing a signed proposal or an error -#[derive(Clone, PartialEq, DomainType)] -#[rawtype(RawSignedProposalResponse)] +#[derive(Clone, PartialEq)] pub struct SignedProposalResponse { pub proposal: Option, pub error: Option, diff --git a/tendermint/src/amino_types/vote.rs b/tendermint/src/amino_types/vote.rs index 6d3d950ad..09bfe835d 100644 --- a/tendermint/src/amino_types/vote.rs +++ b/tendermint/src/amino_types/vote.rs @@ -24,9 +24,10 @@ use tendermint_proto::DomainType; const VALIDATOR_ADDR_SIZE: usize = 20; +impl DomainType for Vote {} + /// Vote represents a prevote, precommit, or commit vote from validators for consensus. -#[derive(Clone, PartialEq, Default, Debug, DomainType)] -#[rawtype(RawVote)] +#[derive(Clone, PartialEq, Default, Debug)] pub struct Vote { pub vote_type: u16, pub height: u32, @@ -119,9 +120,10 @@ impl block::ParseHeight for Vote { } } +impl DomainType for SignVoteRequest {} + /// SignVoteRequest is a request to sign a vote -#[derive(Clone, PartialEq, Debug, DomainType)] -#[rawtype(RawSignVoteRequest)] +#[derive(Clone, PartialEq, Debug)] pub struct SignVoteRequest { pub vote: Option, pub chain_id: String, @@ -188,8 +190,9 @@ impl TryFrom for RawSignedVoteResponse { } } -#[derive(Clone, PartialEq, DomainType)] -#[rawtype(RawCanonicalVote)] +impl DomainType for CanonicalVote {} + +#[derive(Clone, PartialEq)] pub struct CanonicalVote { pub vote_type: u16, pub height: i64, @@ -576,7 +579,7 @@ mod tests { ], }; let mut got = vec![]; - let _have = vote.clone().encode(&mut got); + let _have = vote.encode(&mut got); let v = Vote::decode(got.as_ref()).unwrap(); assert_eq!(v, vote); @@ -587,7 +590,7 @@ mod tests { chain_id: "test_chain_id".to_string(), }; let mut got = vec![]; - let _have = svr.clone().encode(&mut got); + let _have = svr.encode(&mut got); let svr2 = SignVoteRequest::decode(got.as_ref()).unwrap(); assert_eq!(svr, svr2); diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index 07755e6ed..8b10bbde6 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -127,13 +127,14 @@ impl Info { } } +impl DomainType for SimpleValidator {} + /// SimpleValidator is the form of the validator used for computing the Merkle tree. /// It does not include the address, as that is redundant with the pubkey, /// nor the proposer priority, as that changes with every block even if the validator set didn't. /// It contains only the pubkey and the voting power, and is amino encoded. /// TODO: currently only works for Ed25519 pubkeys -#[derive(Clone, PartialEq, DomainType)] -#[rawtype(RawSimpleValidator)] +#[derive(Clone, PartialEq)] pub struct SimpleValidator { /// Public key pub pub_key: Option,