diff --git a/Cargo.lock b/Cargo.lock index d4f20132d53..e6a90208b16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -588,14 +588,6 @@ dependencies = [ "heapsize 0.4.2 (git+https://github.com/cheme/heapsize.git?branch=ec-macfix)", ] -[[package]] -name = "enum_primitive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "env_logger" version = "0.5.13" @@ -1116,7 +1108,6 @@ name = "ethcore-sync" version = "1.12.0" dependencies = [ "common-types 0.1.0", - "enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.12.0", "ethcore-io 1.12.0", @@ -1141,6 +1132,7 @@ dependencies = [ "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syncpacket 0.1.0", "trace-time 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "triehash-ethereum 0.2.0", ] @@ -3664,6 +3656,16 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syncpacket" +version = "0.1.0" +dependencies = [ + "ethcore-network 1.12.0", + "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synom" version = "0.11.3" @@ -4519,7 +4521,6 @@ dependencies = [ "checksum edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd26878c3d921f89797a4e1a1711919f999a9f6946bb6f5a4ffda126d297b7e" "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" "checksum elastic-array 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "88d4851b005ef16de812ea9acdb7bece2f0a40dd86c07b85631d7dafa54537bb" -"checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" "checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" "checksum eth-secp256k1 0.5.7 (git+https://github.com/paritytech/rust-secp256k1)" = "" diff --git a/ethcore/sync/Cargo.toml b/ethcore/sync/Cargo.toml index 9db93316261..fa33c6a033a 100644 --- a/ethcore/sync/Cargo.toml +++ b/ethcore/sync/Cargo.toml @@ -9,7 +9,6 @@ authors = ["Parity Technologies "] [dependencies] common-types = { path = "../types" } -enum_primitive = "0.1.1" ethcore = { path = ".." } ethcore-io = { path = "../../util/io" } ethcore-light = { path = "../light" } @@ -30,6 +29,7 @@ parity-bytes = "0.1" parking_lot = "0.7" rand = "0.4" rlp = { version = "0.3.0", features = ["ethereum"] } +syncpacket = { path = "../../util/syncpacket" } trace-time = "0.1" triehash-ethereum = {version = "0.2", path = "../../util/triehash-ethereum" } diff --git a/ethcore/sync/src/chain/handler.rs b/ethcore/sync/src/chain/handler.rs index 63ab8916139..e94e852dbdb 100644 --- a/ethcore/sync/src/chain/handler.rs +++ b/ethcore/sync/src/chain/handler.rs @@ -17,7 +17,6 @@ use api::WARP_SYNC_PROTOCOL_ID; use block_sync::{BlockDownloaderImportError as DownloaderImportError, DownloadAction}; use bytes::Bytes; -use enum_primitive::FromPrimitive; use ethcore::error::{Error as EthcoreError, ErrorKind as EthcoreErrorKind, ImportErrorKind, BlockError}; use ethcore::snapshot::{ManifestData, RestorationStatus}; use ethcore::verification::queue::kind::blocks::Unverified; diff --git a/ethcore/sync/src/chain/supplier.rs b/ethcore/sync/src/chain/supplier.rs index 7e71e6aeec7..73e9244516a 100644 --- a/ethcore/sync/src/chain/supplier.rs +++ b/ethcore/sync/src/chain/supplier.rs @@ -15,7 +15,6 @@ // along with Parity Ethereum. If not, see . use bytes::Bytes; -use enum_primitive::FromPrimitive; use ethereum_types::H256; use network::{self, PeerId}; use parking_lot::RwLock; diff --git a/ethcore/sync/src/chain/sync_packet.rs b/ethcore/sync/src/chain/sync_packet.rs index 3891090f65e..69fb6e3578c 100644 --- a/ethcore/sync/src/chain/sync_packet.rs +++ b/ethcore/sync/src/chain/sync_packet.rs @@ -22,8 +22,8 @@ //! to convert to/from the packet id values transmitted over the //! wire. -use api::{ETH_PROTOCOL, WARP_SYNC_PROTOCOL_ID}; -use network::{PacketId, ProtocolId}; +use syncpacket::SyncPackets; +use crate::api::{ETH_PROTOCOL, WARP_SYNC_PROTOCOL_ID}; /// An enum that defines all known packet ids in the context of /// synchronization and provides a mechanism to convert from @@ -31,111 +31,28 @@ use network::{PacketId, ProtocolId}; /// to enum variants. This implicitly provides a mechanism to /// check whether a given packet id is known, and to prevent /// packet id clashes when defining new ids. -enum_from_primitive! { -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum SyncPacket { - StatusPacket = 0x00, - NewBlockHashesPacket = 0x01, - TransactionsPacket = 0x02, - GetBlockHeadersPacket = 0x03, - BlockHeadersPacket = 0x04, - GetBlockBodiesPacket = 0x05, - BlockBodiesPacket = 0x06, - NewBlockPacket = 0x07, - - GetNodeDataPacket = 0x0d, - NodeDataPacket = 0x0e, - GetReceiptsPacket = 0x0f, - ReceiptsPacket = 0x10, - - GetSnapshotManifestPacket = 0x11, - SnapshotManifestPacket = 0x12, - GetSnapshotDataPacket = 0x13, - SnapshotDataPacket = 0x14, - ConsensusDataPacket = 0x15, - PrivateTransactionPacket = 0x16, - SignedPrivateTransactionPacket = 0x17, -} -} - -use self::SyncPacket::*; - -/// Provide both subprotocol and packet id information within the -/// same object. -pub trait PacketInfo { - fn id(&self) -> PacketId; - fn protocol(&self) -> ProtocolId; -} - -// The mechanism to match packet ids and protocol may be improved -// through some macro magic, but for now this works. -impl PacketInfo for SyncPacket { - fn protocol(&self) -> ProtocolId { - match self { - StatusPacket | - NewBlockHashesPacket | - TransactionsPacket | - GetBlockHeadersPacket | - BlockHeadersPacket | - GetBlockBodiesPacket | - BlockBodiesPacket | - NewBlockPacket | - - GetNodeDataPacket| - NodeDataPacket | - GetReceiptsPacket | - ReceiptsPacket - - => ETH_PROTOCOL, - - GetSnapshotManifestPacket| - SnapshotManifestPacket | - GetSnapshotDataPacket | - SnapshotDataPacket | - ConsensusDataPacket | - PrivateTransactionPacket | - SignedPrivateTransactionPacket - => WARP_SYNC_PROTOCOL_ID, - } - } - - fn id(&self) -> PacketId { - (*self) as PacketId - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - use enum_primitive::FromPrimitive; - - #[test] - fn packet_ids_from_u8_when_from_primitive_zero_then_equals_status_packet() { - assert_eq!(SyncPacket::from_u8(0x00), Some(StatusPacket)); - } - - #[test] - fn packet_ids_from_u8_when_from_primitive_eleven_then_equals_get_snapshot_manifest_packet() { - assert_eq!(SyncPacket::from_u8(0x11), Some(GetSnapshotManifestPacket)); - } - - #[test] - fn packet_ids_from_u8_when_invalid_packet_id_then_none() { - assert!(SyncPacket::from_u8(0x99).is_none()); - } - - #[test] - fn when_status_packet_then_id_and_protocol_match() { - assert_eq!(StatusPacket.id(), StatusPacket as PacketId); - assert_eq!(StatusPacket.protocol(), ETH_PROTOCOL); - } - - #[test] - fn when_consensus_data_packet_then_id_and_protocol_match() { - assert_eq!(ConsensusDataPacket.id(), ConsensusDataPacket as PacketId); - assert_eq!(ConsensusDataPacket.protocol(), WARP_SYNC_PROTOCOL_ID); - } +#[derive(SyncPackets, Clone, Copy)] +pub enum SyncPacket { + #[protocol(ETH_PROTOCOL)] StatusPacket = 0x00, + #[protocol(ETH_PROTOCOL)] NewBlockHashesPacket = 0x01, + #[protocol(ETH_PROTOCOL)] TransactionsPacket = 0x02, + #[protocol(ETH_PROTOCOL)] GetBlockHeadersPacket = 0x03, + #[protocol(ETH_PROTOCOL)] BlockHeadersPacket = 0x04, + #[protocol(ETH_PROTOCOL)] GetBlockBodiesPacket = 0x05, + #[protocol(ETH_PROTOCOL)] BlockBodiesPacket = 0x06, + #[protocol(ETH_PROTOCOL)] NewBlockPacket = 0x07, + + #[protocol(ETH_PROTOCOL)] GetNodeDataPacket = 0x0d, + #[protocol(ETH_PROTOCOL)] NodeDataPacket = 0x0e, + #[protocol(ETH_PROTOCOL)] GetReceiptsPacket = 0x0f, + #[protocol(ETH_PROTOCOL)] ReceiptsPacket = 0x10, + + #[protocol(WARP_SYNC_PROTOCOL_ID)] GetSnapshotManifestPacket = 0x11, + #[protocol(WARP_SYNC_PROTOCOL_ID)] SnapshotManifestPacket = 0x12, + #[protocol(WARP_SYNC_PROTOCOL_ID)] GetSnapshotDataPacket = 0x13, + #[protocol(WARP_SYNC_PROTOCOL_ID)] SnapshotDataPacket = 0x14, + #[protocol(WARP_SYNC_PROTOCOL_ID)] ConsensusDataPacket = 0x15, + #[protocol(WARP_SYNC_PROTOCOL_ID)] PrivateTransactionPacket = 0x16, + #[protocol(WARP_SYNC_PROTOCOL_ID)] SignedPrivateTransactionPacket = 0x17, } diff --git a/ethcore/sync/src/lib.rs b/ethcore/sync/src/lib.rs index 8a1e19569a4..9ce5c93bf41 100644 --- a/ethcore/sync/src/lib.rs +++ b/ethcore/sync/src/lib.rs @@ -35,6 +35,7 @@ extern crate parity_bytes as bytes; extern crate parking_lot; extern crate rand; extern crate rlp; +extern crate syncpacket; extern crate triehash_ethereum; extern crate ethcore_light as light; @@ -44,8 +45,6 @@ extern crate ethcore_light as light; #[cfg(test)] extern crate kvdb_memorydb; #[cfg(test)] extern crate rustc_hex; -#[macro_use] -extern crate enum_primitive; #[macro_use] extern crate macros; #[macro_use] diff --git a/util/syncpacket/Cargo.toml b/util/syncpacket/Cargo.toml new file mode 100644 index 00000000000..635aa667b05 --- /dev/null +++ b/util/syncpacket/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "syncpacket" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[lib] +name = "syncpacket" +proc-macro = true + +[dependencies] +syn = "0.15" +quote = "0.6" +proc-macro2 = "0.4" + +[dev-dependencies] +ethcore-network = {path = "../network"} diff --git a/util/syncpacket/src/lib.rs b/util/syncpacket/src/lib.rs new file mode 100644 index 00000000000..c6e98d3a4aa --- /dev/null +++ b/util/syncpacket/src/lib.rs @@ -0,0 +1,164 @@ +// Copyright 2015-2019 Parity Technologies (UK) Ltd. +// This file is part of Parity Ethereum. + +// Parity Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Ethereum. If not, see . + +#![recursion_limit="128"] + +// Needs to be "extern crate" even in rust 2018: +// https://blog.rust-lang.org/2018/12/21/Procedural-Macros-in-Rust-2018.html +extern crate proc_macro; + +use proc_macro2::Ident; +use quote::quote; +use syn::{Attribute, Data, DeriveInput, Expr, Meta, MetaList, NestedMeta, Result, Variant}; +use syn::punctuated::Punctuated; +use syn::token::Comma; + +/// Given a list of arguments to the "protocol" attribute, check +/// that there is only one argument and return it +fn parse_protocol_arguments(args: &MetaList) -> Result<&Ident> { + if args.nested.len() != 1 { + return Err(syn::Error::new_spanned(args, "protocol attribute should have exactly one argument")); + } + + // We have exactly one argument, should not panic + match args.nested.first().expect("protocol attribute without value").value() { + // Meta argument + NestedMeta::Meta(meta) => match meta { + Meta::Word(ident) => Ok(&ident), + _ => return Err(syn::Error::new_spanned(meta, "nested arguments to protocol are not allowed")), + }, + // Quoted string + a @ _ => return Err(syn::Error::new_spanned(a, "protocol argument must be an unquoted identifier")) + } +} + +/// Helper function to parse arguments to the protocol attribute. +/// Syntax should be #[protocol(P)] PacketName = 0xNN, +fn parse_protocol_attribute(input: &Attribute) -> Result { + let argument = match input.parse_meta() { + Ok(arg) => arg, + Err(err) => return Err(err) + }; + + // Arguments to invocation attributes are delivered as a list + match argument { + Meta::Word(_) => Err(syn::Error::new_spanned(input, "protocol attribute without argument")), + Meta::List(args) => parse_protocol_arguments(&args).map(|ok| ok.clone()), + _ => Err(syn::Error::new_spanned(input, "unsupported syntax")) + } +} + +/// For each variant in the enum check that it has a "protocol" +/// attribute. If found, parse its arguments and return one +/// single protocol id. +fn get_variant_protocol_idents(variants: &Punctuated) -> Result> { + variants.iter() + .map( + |v| v.attrs + .iter() + .find(|&x| x.path.is_ident("protocol")) + .ok_or(syn::Error::new_spanned(v, format!("enum variant without protocol attribute {}", &v.ident))) + .and_then(|ref a| parse_protocol_attribute(a)) + ).collect() +} + +/// For each variant in the enum make sure it has a numeric value +/// assigned and return it +fn get_variant_values(variants: &Punctuated) -> Result> { + variants.iter() + .map( + |v| v.discriminant + .as_ref() + .map(|d| &d.1) + .ok_or(syn::Error::new_spanned(v, format!("enum pattern {} is not discriminant; should have assigned a unique value such as Foo = 1", &v.ident))) + ) + .collect() +} + +/// The SyncPackets derive-macro will provide an enum with this attribute: +/// +/// * With a method "from_u8" which will optionally convert a u8 value to +/// one of the variants or None if the value is unknown. +/// +/// * With an implementation of a trait PacketInfo to get the packet id and +/// the protocol from instances of the enum. +#[proc_macro_derive(SyncPackets, attributes(protocol))] +pub fn sync_packets(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Can we ever panic here? + let ast = syn::parse(input).expect("invalid enum syntax"); + + match impl_sync_packets(&ast) { + Ok(output) => output.into(), + Err(err) => err.to_compile_error().into(), + } +} + +/// Actual implementation of the macro SyncPackets +fn impl_sync_packets(ast: &DeriveInput) -> Result { + let body = match ast.data { + Data::Enum(ref e) => e, + _ => return Err(syn::Error::new_spanned(ast, "#[derive(SyncPackets)] is only defined for enums.")), + }; + + let enum_name = &ast.ident; + + if body.variants.is_empty() { + return Err(syn::Error::new_spanned(enum_name, format!("enum {} has no variants defined", enum_name))); + } + + // Apparently quote! consumes interpolated variables. Clone ids + // to use them twice. + let idents_from_u8: Vec<_> = body.variants.iter().map(|v| &v.ident).collect(); + let idents_enum = idents_from_u8.clone(); + + let protocols = get_variant_protocol_idents(&body.variants)?; + let values = get_variant_values(&body.variants)?; + + Ok(quote!{ + use ethcore_network::{PacketId, ProtocolId}; + + impl #enum_name { + pub fn from_u8(id: u8) -> Option<#enum_name> { + match id { + #(#values => Some(#idents_from_u8)),*, + _ => None + + } + } + } + + use self::#enum_name::*; + + /// Provide both subprotocol and packet id information within the + /// same object. + pub trait PacketInfo { + fn id(&self) -> PacketId; + fn protocol(&self) -> ProtocolId; + } + + impl PacketInfo for #enum_name { + fn protocol(&self) -> ProtocolId { + match self { + #(#idents_enum => #protocols),* + } + } + + fn id(&self) -> PacketId { + (*self) as PacketId + } + } + }) +} diff --git a/util/syncpacket/tests/syncpacket.rs b/util/syncpacket/tests/syncpacket.rs new file mode 100644 index 00000000000..a8d53a5363e --- /dev/null +++ b/util/syncpacket/tests/syncpacket.rs @@ -0,0 +1,51 @@ +// Copyright 2015-2019 Parity Technologies (UK) Ltd. +// This file is part of Parity Ethereum. + +// Parity Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Ethereum. If not, see . + +#[macro_use] +extern crate syncpacket; + +const MY_PROTOCOL0: ProtocolId = *b"mp0"; +const MY_PROTOCOL1: ProtocolId = *b"mp1"; + +#[derive(SyncPackets, Clone, Copy, Debug, PartialEq)] +enum Packets { + #[protocol(MY_PROTOCOL0)] Packet0 = 0x00, + #[protocol(MY_PROTOCOL1)] Packet1 = 0x01, +} + +#[test] +fn packet_ids_from_u8_when_from_primitive_zero_then_equals_status_packet() { + assert_eq!(Packets::from_u8(0x00), Some(Packets::Packet0)); +} + +#[test] +fn packet_ids_from_u8_when_from_primitive_eleven_then_equals_get_snapshot_manifest_packet() { + assert_eq!(Packets::from_u8(0x01), Some(Packets::Packet1)); +} + +#[test] +fn packet_ids_from_u8_when_invalid_packet_id_then_none() { + assert!(Packets::from_u8(0x99).is_none()); +} + +#[test] +fn when_status_packet_then_id_and_protocol_match() { + assert_eq!(Packets::Packet0.id(), Packets::Packet0 as PacketId); + assert_eq!(Packets::Packet0.protocol(), MY_PROTOCOL0); + + assert_eq!(Packets::Packet1.id(), Packets::Packet1 as PacketId); + assert_eq!(Packets::Packet1.protocol(), MY_PROTOCOL1); +}