diff --git a/Cargo.lock b/Cargo.lock index 6ccc3ab90..ee7578ebc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "build_const" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" + [[package]] name = "byte-slice-cast" version = "1.2.1" @@ -244,6 +250,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -376,6 +391,19 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ethereum-forkid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b823f6b913b97e58a2bd67a7beeb48b0338d4aa8e3cc21d9cdab457716e4d4" +dependencies = [ + "crc", + "fastrlp", + "maplit", + "primitive-types", + "thiserror", +] + [[package]] name = "ethereum-types" version = "0.13.1" @@ -400,7 +428,7 @@ dependencies = [ "chrono", "elliptic-curve", "ethabi", - "fastrlp 0.1.3", + "fastrlp", "generic-array", "hex", "k256", @@ -438,17 +466,6 @@ dependencies = [ "fastrlp-derive", ] -[[package]] -name = "fastrlp" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9244dd9f0a2b54815814926640439146f469837ec4141e33354b6b258a0493" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - [[package]] name = "fastrlp-derive" version = "0.1.2" @@ -684,6 +701,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "hmac" version = "0.12.1" @@ -1012,6 +1035,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "mdbx-sys" version = "0.12.1-0" @@ -1431,6 +1460,20 @@ dependencies = [ "libmdbx", ] +[[package]] +name = "reth-eth-wire" +version = "0.1.0" +dependencies = [ + "bytes", + "ethereum-forkid", + "ethers-core", + "fastrlp", + "hex", + "hex-literal", + "reth-primitives", + "thiserror", +] + [[package]] name = "reth-executor" version = "0.1.0" @@ -1465,7 +1508,7 @@ version = "0.1.0" dependencies = [ "bytes", "ethers-core", - "fastrlp 0.2.0", + "fastrlp", ] [[package]] @@ -1493,7 +1536,7 @@ dependencies = [ name = "reth-rpc-types" version = "0.1.0" dependencies = [ - "fastrlp 0.1.3", + "fastrlp", "reth-primitives", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 6ee2d322d..4a98481bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/executor", "crates/interfaces", "crates/net/p2p", + "crates/net/eth-wire", "crates/net/rpc", "crates/net/rpc-api", "crates/net/rpc-types", diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml new file mode 100644 index 000000000..061f42f08 --- /dev/null +++ b/crates/net/eth-wire/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "reth-eth-wire" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/foundry-rs/reth" +readme = "README.md" + +[dependencies] +bytes = { version = "1.1" } + +# can remove these restrictions once ethereum-types is bumped across the board +fastrlp = { version = "0.1.3", features = ["alloc", "derive", "std", "ethereum-types"] } +ethereum-forkid = "=0.10" +hex = "0.4" +thiserror = "1" + +# reth +reth-primitives = { path = "../../primitives" } + +[dev-dependencies] +hex-literal = "0.3" +ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } diff --git a/crates/net/eth-wire/src/lib.rs b/crates/net/eth-wire/src/lib.rs new file mode 100644 index 000000000..b88f371e7 --- /dev/null +++ b/crates/net/eth-wire/src/lib.rs @@ -0,0 +1,14 @@ +#![warn(missing_docs, unreachable_pub)] +#![deny(unused_must_use, rust_2018_idioms)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] + +//! Types for the eth wire protocol. + +mod status; +pub use status::Status; + +mod version; +pub use version::EthVersion; diff --git a/crates/net/eth-wire/src/status.rs b/crates/net/eth-wire/src/status.rs new file mode 100644 index 000000000..13ff2acaf --- /dev/null +++ b/crates/net/eth-wire/src/status.rs @@ -0,0 +1,210 @@ +use ethereum_forkid::ForkId; +use fastrlp::{RlpDecodable, RlpEncodable}; +use reth_primitives::{Chain, H256, U256}; +use std::fmt::{Debug, Display}; + +/// The status message is used in the eth protocol handshake to ensure that peers are on the same +/// network and are following the same fork. +/// The total difficulty and best block hash are used to identify whether or not the requesting +/// client should be sent historical blocks for a full blockchain sync. +/// +/// When performing a handshake, the total difficulty is not guaranteed to correspond to the block +/// hash. This information should be treated as untrusted. +#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] +pub struct Status { + /// The current protocol version. For example, peers running `eth/66` would have a version of + /// 66. + pub version: u8, + + /// The chain id, as introduced in + /// [EIP155](https://eips.ethereum.org/EIPS/eip-155#list-of-chain-ids). + pub chain: Chain, + + /// Total difficulty of the best chain. + pub total_difficulty: U256, + + /// The highest difficulty block hash the peer has seen + pub blockhash: H256, + + /// The genesis hash of the peer's chain. + pub genesis: H256, + + /// The fork identifier, a [CRC32 + /// checksum](https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm) for + /// identifying the peer's fork as defined by + /// [EIP-2124](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md). + /// This was added in [`eth/64`](https://eips.ethereum.org/EIPS/eip-2364) + pub forkid: ForkId, +} + +impl Display for Status { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hexed_blockhash = hex::encode(self.blockhash); + let hexed_genesis = hex::encode(self.genesis); + write!( + f, + "Status {{ version: {}, chain: {}, total_difficulty: {}, blockhash: {}, genesis: {}, forkid: {:X?} }}", + self.version, + self.chain, + self.total_difficulty, + hexed_blockhash, + hexed_genesis, + self.forkid + ) + } +} + +impl Debug for Status { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hexed_blockhash = hex::encode(self.blockhash); + let hexed_genesis = hex::encode(self.genesis); + if f.alternate() { + write!( + f, + "Status {{\n\tversion: {:?},\n\tchain: {:?},\n\ttotal_difficulty: {:?},\n\tblockhash: {},\n\tgenesis: {},\n\tforkid: {:X?}\n}}", + self.version, + self.chain, + self.total_difficulty, + hexed_blockhash, + hexed_genesis, + self.forkid + ) + } else { + write!( + f, + "Status {{ version: {:?}, chain: {:?}, total_difficulty: {:?}, blockhash: {}, genesis: {}, forkid: {:X?} }}", + self.version, + self.chain, + self.total_difficulty, + hexed_blockhash, + hexed_genesis, + self.forkid + ) + } + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use ethereum_forkid::{ForkHash, ForkId}; + use ethers_core::types::Chain as NamedChain; + use fastrlp::{Decodable, Encodable}; + use hex_literal::hex; + use reth_primitives::{Chain, H256, U256}; + + use crate::{EthVersion, Status}; + + #[test] + fn encode_eth_status_message() { + let expected = hex!("f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"); + let status = Status { + version: EthVersion::Eth67 as u8, + chain: Chain::Named(NamedChain::Mainnet), + total_difficulty: U256::from(36206751599115524359527u128), + blockhash: H256::from_str( + "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d", + ) + .unwrap(), + genesis: H256::from_str( + "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + ) + .unwrap(), + forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 }, + }; + + let mut rlp_status = vec![]; + status.encode(&mut rlp_status); + assert_eq!(rlp_status, expected); + } + + #[test] + fn decode_eth_status_message() { + let data = hex!("f85643018a07aac59dabcdd74bc567a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13da0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d80"); + let expected = Status { + version: EthVersion::Eth67 as u8, + chain: Chain::Named(NamedChain::Mainnet), + total_difficulty: U256::from(36206751599115524359527u128), + blockhash: H256::from_str( + "feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d", + ) + .unwrap(), + genesis: H256::from_str( + "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + ) + .unwrap(), + forkid: ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 }, + }; + let status = Status::decode(&mut &data[..]).unwrap(); + assert_eq!(status, expected); + } + + #[test] + fn encode_network_status_message() { + let expected = hex!("f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"); + let status = Status { + version: EthVersion::Eth66 as u8, + chain: Chain::Named(NamedChain::BinanceSmartChain), + total_difficulty: U256::from(37851386u64), + blockhash: H256::from_str( + "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b", + ) + .unwrap(), + genesis: H256::from_str( + "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b", + ) + .unwrap(), + forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 }, + }; + + let mut rlp_status = vec![]; + status.encode(&mut rlp_status); + assert_eq!(rlp_status, expected); + } + + #[test] + fn decode_network_status_message() { + let data = hex!("f850423884024190faa0f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27ba00d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5bc6845d43d2fd80"); + let expected = Status { + version: EthVersion::Eth66 as u8, + chain: Chain::Named(NamedChain::BinanceSmartChain), + total_difficulty: U256::from(37851386u64), + blockhash: H256::from_str( + "f8514c4680ef27700751b08f37645309ce65a449616a3ea966bf39dd935bb27b", + ) + .unwrap(), + genesis: H256::from_str( + "0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b", + ) + .unwrap(), + forkid: ForkId { hash: ForkHash([0x5d, 0x43, 0xd2, 0xfd]), next: 0 }, + }; + let status = Status::decode(&mut &data[..]).unwrap(); + assert_eq!(status, expected); + } + + #[test] + fn decode_another_network_status_message() { + let data = hex!("f86142820834936d68fcffffffffffffffffffffffffdeab81b8a0523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0ca06499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bdc6841a67ccd880"); + let expected = Status { + version: EthVersion::Eth66 as u8, + chain: Chain::Id(2100), + total_difficulty: U256::from_str( + "0x000000000000000000000000006d68fcffffffffffffffffffffffffdeab81b8", + ) + .unwrap(), + blockhash: H256::from_str( + "523e8163a6d620a4cc152c547a05f28a03fec91a2a615194cb86df9731372c0c", + ) + .unwrap(), + genesis: H256::from_str( + "6499dccdc7c7def3ebb1ce4c6ee27ec6bd02aee570625ca391919faf77ef27bd", + ) + .unwrap(), + forkid: ForkId { hash: ForkHash([0x1a, 0x67, 0xcc, 0xd8]), next: 0 }, + }; + let status = Status::decode(&mut &data[..]).unwrap(); + assert_eq!(status, expected); + } +} diff --git a/crates/net/eth-wire/src/version.rs b/crates/net/eth-wire/src/version.rs new file mode 100644 index 000000000..8bf2ba8b1 --- /dev/null +++ b/crates/net/eth-wire/src/version.rs @@ -0,0 +1,107 @@ +use std::str::FromStr; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +#[error("Unknown eth protocol version: {0}")] +pub struct ParseVersionError(String); + +/// The `eth` protocol version. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum EthVersion { + /// The `eth` protocol version 66. + Eth66 = 66, + + /// The `eth` protocol version 67. + Eth67 = 67, +} + +/// Allow for converting from a `&str` to an `EthVersion`. +/// +/// # Example +/// ``` +/// use reth_eth_wire::EthVersion; +/// +/// let version = EthVersion::try_from("67").unwrap(); +/// assert_eq!(version, EthVersion::Eth67); +/// ``` +impl TryFrom<&str> for EthVersion { + type Error = ParseVersionError; + + #[inline] + fn try_from(s: &str) -> Result { + match s { + "66" => Ok(EthVersion::Eth66), + "67" => Ok(EthVersion::Eth67), + _ => Err(ParseVersionError(s.to_string())), + } + } +} + +/// Allow for converting from a u8 to an `EthVersion`. +/// +/// # Example +/// ``` +/// use reth_eth_wire::EthVersion; +/// +/// let version = EthVersion::try_from(67).unwrap(); +/// assert_eq!(version, EthVersion::Eth67); +/// ``` +impl TryFrom for EthVersion { + type Error = ParseVersionError; + + #[inline] + fn try_from(u: u8) -> Result { + match u { + 66 => Ok(EthVersion::Eth66), + 67 => Ok(EthVersion::Eth67), + _ => Err(ParseVersionError(u.to_string())), + } + } +} + +impl FromStr for EthVersion { + type Err = ParseVersionError; + + #[inline] + fn from_str(s: &str) -> Result { + EthVersion::try_from(s) + } +} + +impl From for u8 { + #[inline] + fn from(v: EthVersion) -> u8 { + v as u8 + } +} + +impl From for &'static str { + #[inline] + fn from(v: EthVersion) -> &'static str { + match v { + EthVersion::Eth66 => "66", + EthVersion::Eth67 => "67", + } + } +} + +#[cfg(test)] +mod test { + use super::{EthVersion, ParseVersionError}; + use std::{convert::TryFrom, string::ToString}; + + #[test] + fn test_eth_version_try_from_str() { + assert_eq!(EthVersion::Eth66, EthVersion::try_from("66").unwrap()); + assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap()); + assert_eq!(Err(ParseVersionError("68".to_string())), EthVersion::try_from("68")); + } + + #[test] + fn test_eth_version_from_str() { + assert_eq!(EthVersion::Eth66, "66".parse().unwrap()); + assert_eq!(EthVersion::Eth67, "67".parse().unwrap()); + assert_eq!(Err(ParseVersionError("68".to_string())), "68".parse::()); + } +} diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 3157934d1..c8b411d6e 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -8,6 +8,6 @@ readme = "README.md" description = "Commonly used types in reth." [dependencies] -fastrlp = { version = "0.2.0" } +fastrlp = { version = "0.1.3" } ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } bytes = "1.2" \ No newline at end of file diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 93d8c1d35..6405977cc 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -17,6 +17,7 @@ mod transaction; pub use account::Account; pub use block::{Block, BlockLocked}; +pub use chain::Chain; pub use header::{Header, HeaderLocked}; pub use log::Log; pub use receipt::Receipt;