feat: add eth-wire (#20)

This commit is contained in:
Dan Cline
2022-10-10 12:46:02 -04:00
committed by GitHub
parent a759201b40
commit d7c8b70cc3
8 changed files with 414 additions and 15 deletions

View File

@ -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 }

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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<Self, Self::Error> {
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<u8> for EthVersion {
type Error = ParseVersionError;
#[inline]
fn try_from(u: u8) -> Result<Self, Self::Error> {
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<Self, Self::Err> {
EthVersion::try_from(s)
}
}
impl From<EthVersion> for u8 {
#[inline]
fn from(v: EthVersion) -> u8 {
v as u8
}
}
impl From<EthVersion> 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::<EthVersion>());
}
}