Skip to content
56 changes: 56 additions & 0 deletions crates/net/eth-wire-types/src/block_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Block range related types introduced in eth/70 (EIP-7975).

use alloy_rlp::{RlpDecodable, RlpEncodable};
use reth_codecs_derive::add_arbitrary_tests;

/// The block range a peer can currently serve (inclusive bounds).
#[derive(Clone, Copy, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[add_arbitrary_tests(rlp)]
pub struct BlockRange {
/// Earliest block number the peer can serve.
pub start_block: u64,
/// Latest block number the peer can serve.
pub end_block: u64,
}

impl BlockRange {
/// Returns true if the start/end pair forms a valid range.
pub const fn is_valid(&self) -> bool {
self.start_block <= self.end_block
}
}

/// eth/70 request for the peer's current block range.
///
/// The payload is empty; the request id is carried by the [`crate::message::RequestPair`].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[add_arbitrary_tests(rlp)]
pub struct RequestBlockRange;

/// eth/70 response to [`RequestBlockRange`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[add_arbitrary_tests(rlp)]
pub struct SendBlockRange {
/// The earliest block which is available.
pub start_block: u64,
/// The latest block which is available.
pub end_block: u64,
}

impl SendBlockRange {
/// Returns the contained range.
pub const fn as_range(&self) -> BlockRange {
BlockRange { start_block: self.start_block, end_block: self.end_block }
}

/// Constructs from a [`BlockRange`].
pub const fn from_range(range: BlockRange) -> Self {
Self { start_block: range.start_block, end_block: range.end_block }
}
}
2 changes: 1 addition & 1 deletion crates/net/eth-wire-types/src/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl NewPooledTransactionHashes {
matches!(version, EthVersion::Eth67 | EthVersion::Eth66)
}
Self::Eth68(_) => {
matches!(version, EthVersion::Eth68 | EthVersion::Eth69)
matches!(version, EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70)
}
}
}
Expand Down
50 changes: 47 additions & 3 deletions crates/net/eth-wire-types/src/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ impl Capability {
Self::eth(EthVersion::Eth68)
}

/// Returns the [`EthVersion::Eth69`] capability.
pub const fn eth_69() -> Self {
Self::eth(EthVersion::Eth69)
}

/// Returns the [`EthVersion::Eth70`] capability.
pub const fn eth_70() -> Self {
Self::eth(EthVersion::Eth70)
}

/// Whether this is eth v66 protocol.
#[inline]
pub fn is_eth_v66(&self) -> bool {
Expand All @@ -118,10 +128,26 @@ impl Capability {
self.name == "eth" && self.version == 68
}

/// Whether this is eth v69.
#[inline]
pub fn is_eth_v69(&self) -> bool {
self.name == "eth" && self.version == 69
}

/// Whether this is eth v70.
#[inline]
pub fn is_eth_v70(&self) -> bool {
self.name == "eth" && self.version == 70
}

/// Whether this is any eth version.
#[inline]
pub fn is_eth(&self) -> bool {
self.is_eth_v66() || self.is_eth_v67() || self.is_eth_v68()
self.is_eth_v66() ||
self.is_eth_v67() ||
self.is_eth_v68() ||
self.is_eth_v69() ||
self.is_eth_v70()
}
}

Expand All @@ -141,7 +167,7 @@ impl From<EthVersion> for Capability {
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Capability {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let version = u.int_in_range(66..=69)?; // Valid eth protocol versions are 66-69
let version = u.int_in_range(66..=70)?; // Valid eth protocol versions are 66-70
// Only generate valid eth protocol name for now since it's the only supported protocol
Ok(Self::new_static("eth", version))
}
Expand All @@ -155,6 +181,8 @@ pub struct Capabilities {
eth_66: bool,
eth_67: bool,
eth_68: bool,
eth_69: bool,
eth_70: bool,
}

impl Capabilities {
Expand All @@ -164,6 +192,8 @@ impl Capabilities {
eth_66: value.iter().any(Capability::is_eth_v66),
eth_67: value.iter().any(Capability::is_eth_v67),
eth_68: value.iter().any(Capability::is_eth_v68),
eth_69: value.iter().any(Capability::is_eth_v69),
eth_70: value.iter().any(Capability::is_eth_v70),
inner: value,
}
}
Expand All @@ -182,7 +212,7 @@ impl Capabilities {
/// Whether the peer supports `eth` sub-protocol.
#[inline]
pub const fn supports_eth(&self) -> bool {
self.eth_68 || self.eth_67 || self.eth_66
self.eth_70 || self.eth_69 || self.eth_68 || self.eth_67 || self.eth_66
}

/// Whether this peer supports eth v66 protocol.
Expand All @@ -202,6 +232,18 @@ impl Capabilities {
pub const fn supports_eth_v68(&self) -> bool {
self.eth_68
}

/// Whether this peer supports eth v69 protocol.
#[inline]
pub const fn supports_eth_v69(&self) -> bool {
self.eth_69
}

/// Whether this peer supports eth v70 protocol.
#[inline]
pub const fn supports_eth_v70(&self) -> bool {
self.eth_70
}
}

impl From<Vec<Capability>> for Capabilities {
Expand All @@ -224,6 +266,8 @@ impl Decodable for Capabilities {
eth_66: inner.iter().any(Capability::is_eth_v66),
eth_67: inner.iter().any(Capability::is_eth_v67),
eth_68: inner.iter().any(Capability::is_eth_v68),
eth_69: inner.iter().any(Capability::is_eth_v69),
eth_70: inner.iter().any(Capability::is_eth_v70),
inner,
})
}
Expand Down
5 changes: 4 additions & 1 deletion crates/net/eth-wire-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
extern crate alloc;

mod status;
pub use status::{Status, StatusBuilder, StatusEth69, StatusMessage, UnifiedStatus};
pub use status::{Status, StatusBuilder, StatusEth69, StatusEth70, StatusMessage, UnifiedStatus};

mod block_range;
pub use block_range::{BlockRange, RequestBlockRange, SendBlockRange};

pub mod version;
pub use version::{EthVersion, ProtocolVersion};
Expand Down
60 changes: 48 additions & 12 deletions crates/net/eth-wire-types/src/message.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Implements Ethereum wire protocol for versions 66, 67, and 68.
//! Implements Ethereum wire protocol for versions 66 through 70.
//! Defines structs/enums for messages, request-response pairs, and broadcasts.
//! Handles compatibility with [`EthVersion`].
//!
Expand All @@ -9,8 +9,8 @@
use super::{
broadcast::NewBlockHashes, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders,
GetNodeData, GetPooledTransactions, GetReceipts, NewPooledTransactionHashes66,
NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, StatusEth69,
Transactions,
NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, RequestBlockRange,
SendBlockRange, Status, StatusEth69, StatusEth70, Transactions,
};
use crate::{
status::StatusMessage, BlockRangeUpdate, EthNetworkPrimitives, EthVersion, NetworkPrimitives,
Expand Down Expand Up @@ -66,10 +66,12 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
// For EIP-7642 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7642.md):
// pre-merge (legacy) status messages include total difficulty, whereas eth/69 omits it.
let message = match message_type {
EthMessageID::Status => EthMessage::Status(if version < EthVersion::Eth69 {
StatusMessage::Legacy(Status::decode(buf)?)
} else {
EthMessageID::Status => EthMessage::Status(if version >= EthVersion::Eth70 {
StatusMessage::Eth70(StatusEth70::decode(buf)?)
} else if version >= EthVersion::Eth69 {
StatusMessage::Eth69(StatusEth69::decode(buf)?)
} else {
StatusMessage::Legacy(Status::decode(buf)?)
}),
EthMessageID::NewBlockHashes => {
EthMessage::NewBlockHashes(NewBlockHashes::decode(buf)?)
Expand Down Expand Up @@ -99,6 +101,18 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
EthMessageID::PooledTransactions => {
EthMessage::PooledTransactions(RequestPair::decode(buf)?)
}
EthMessageID::RequestBlockRange => {
if version < EthVersion::Eth70 {
return Err(MessageError::Invalid(version, EthMessageID::RequestBlockRange))
}
EthMessage::RequestBlockRange(RequestPair::decode(buf)?)
}
EthMessageID::SendBlockRange => {
if version < EthVersion::Eth70 {
return Err(MessageError::Invalid(version, EthMessageID::SendBlockRange))
}
EthMessage::SendBlockRange(RequestPair::decode(buf)?)
}
EthMessageID::GetNodeData => {
if version >= EthVersion::Eth67 {
return Err(MessageError::Invalid(version, EthMessageID::GetNodeData))
Expand Down Expand Up @@ -205,6 +219,9 @@ impl<N: NetworkPrimitives> From<EthBroadcastMessage<N>> for ProtocolBroadcastMes
///
/// The `eth/69` announces the historical block range served by the node. Removes total difficulty
/// information. And removes the Bloom field from receipts transferred over the protocol.
///
/// The `eth/70` (EIP-7975) extends `Status` with `blockRange` and adds `RequestBlockRange` /
/// `SendBlockRange` messages to query/announce the currently served block interval.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
Expand Down Expand Up @@ -253,6 +270,10 @@ pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
serde(bound = "N::PooledTransaction: serde::Serialize + serde::de::DeserializeOwned")
)]
PooledTransactions(RequestPair<PooledTransactions<N::PooledTransaction>>),
/// Represents a `RequestBlockRange` request-response pair.
RequestBlockRange(RequestPair<RequestBlockRange>),
/// Represents a `SendBlockRange` request-response pair.
SendBlockRange(RequestPair<SendBlockRange>),
/// Represents a `GetNodeData` request-response pair.
GetNodeData(RequestPair<GetNodeData>),
/// Represents a `NodeData` request-response pair.
Expand Down Expand Up @@ -298,6 +319,8 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::BlockBodies(_) => EthMessageID::BlockBodies,
Self::GetPooledTransactions(_) => EthMessageID::GetPooledTransactions,
Self::PooledTransactions(_) => EthMessageID::PooledTransactions,
Self::RequestBlockRange(_) => EthMessageID::RequestBlockRange,
Self::SendBlockRange(_) => EthMessageID::SendBlockRange,
Self::GetNodeData(_) => EthMessageID::GetNodeData,
Self::NodeData(_) => EthMessageID::NodeData,
Self::GetReceipts(_) => EthMessageID::GetReceipts,
Expand All @@ -315,7 +338,8 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::GetBlockHeaders(_) |
Self::GetReceipts(_) |
Self::GetPooledTransactions(_) |
Self::GetNodeData(_)
Self::GetNodeData(_) |
Self::RequestBlockRange(_)
)
}

Expand All @@ -328,7 +352,8 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::Receipts69(_) |
Self::BlockHeaders(_) |
Self::BlockBodies(_) |
Self::NodeData(_)
Self::NodeData(_) |
Self::SendBlockRange(_)
)
}
}
Expand All @@ -348,6 +373,8 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
Self::BlockBodies(bodies) => bodies.encode(out),
Self::GetPooledTransactions(request) => request.encode(out),
Self::PooledTransactions(transactions) => transactions.encode(out),
Self::RequestBlockRange(request) => request.encode(out),
Self::SendBlockRange(range) => range.encode(out),
Self::GetNodeData(request) => request.encode(out),
Self::NodeData(data) => data.encode(out),
Self::GetReceipts(request) => request.encode(out),
Expand All @@ -371,6 +398,8 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
Self::BlockBodies(bodies) => bodies.length(),
Self::GetPooledTransactions(request) => request.length(),
Self::PooledTransactions(transactions) => transactions.length(),
Self::RequestBlockRange(request) => request.length(),
Self::SendBlockRange(range) => range.length(),
Self::GetNodeData(request) => request.length(),
Self::NodeData(data) => data.length(),
Self::GetReceipts(request) => request.length(),
Expand Down Expand Up @@ -452,6 +481,10 @@ pub enum EthMessageID {
GetPooledTransactions = 0x09,
/// Represents pooled transactions.
PooledTransactions = 0x0a,
/// Requests block range (eth/70).
RequestBlockRange = 0x0b,
/// Responds with block range (eth/70).
SendBlockRange = 0x0c,
/// Requests node data.
GetNodeData = 0x0d,
/// Represents node data.
Expand Down Expand Up @@ -483,6 +516,8 @@ impl EthMessageID {
Self::NewPooledTransactionHashes => 0x08,
Self::GetPooledTransactions => 0x09,
Self::PooledTransactions => 0x0a,
Self::RequestBlockRange => 0x0b,
Self::SendBlockRange => 0x0c,
Self::GetNodeData => 0x0d,
Self::NodeData => 0x0e,
Self::GetReceipts => 0x0f,
Expand All @@ -494,10 +529,9 @@ impl EthMessageID {

/// Returns the max value for the given version.
pub const fn max(version: EthVersion) -> u8 {
if version.is_eth69() {
Self::BlockRangeUpdate.to_u8()
} else {
Self::Receipts.to_u8()
match version {
EthVersion::Eth70 | EthVersion::Eth69 => Self::BlockRangeUpdate.to_u8(),
_ => Self::Receipts.to_u8(),
}
}

Expand Down Expand Up @@ -562,6 +596,8 @@ impl TryFrom<usize> for EthMessageID {
0x08 => Ok(Self::NewPooledTransactionHashes),
0x09 => Ok(Self::GetPooledTransactions),
0x0a => Ok(Self::PooledTransactions),
0x0b => Ok(Self::RequestBlockRange),
0x0c => Ok(Self::SendBlockRange),
0x0d => Ok(Self::GetNodeData),
0x0e => Ok(Self::NodeData),
0x0f => Ok(Self::GetReceipts),
Expand Down
Loading
Loading