mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: add eth-wire (#20)
This commit is contained in:
23
crates/net/eth-wire/Cargo.toml
Normal file
23
crates/net/eth-wire/Cargo.toml
Normal 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 }
|
||||
14
crates/net/eth-wire/src/lib.rs
Normal file
14
crates/net/eth-wire/src/lib.rs
Normal 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;
|
||||
210
crates/net/eth-wire/src/status.rs
Normal file
210
crates/net/eth-wire/src/status.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
107
crates/net/eth-wire/src/version.rs
Normal file
107
crates/net/eth-wire/src/version.rs
Normal 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>());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user