Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7856d25
Add eth/70 protocol support and block range messaging
0xKarl98 Dec 5, 2025
70480ac
add comments
0xKarl98 Dec 5, 2025
3b129f5
fix tests
0xKarl98 Dec 5, 2025
13e9c00
touchups
0xKarl98 Dec 5, 2025
14453cc
add more comments to undertsand precisely
0xKarl98 Dec 5, 2025
dd7a10b
cargo fmt
0xKarl98 Dec 5, 2025
90492aa
keep eth/69 once enable full support
0xKarl98 Dec 6, 2025
63ac93a
instantiate based on message variant
0xKarl98 Dec 6, 2025
0d38ba9
optimize instant related code and remove test for now
0xKarl98 Dec 6, 2025
04824d3
cargo fmt
0xKarl98 Dec 6, 2025
f49b954
Throttle range requests using interval ticks
0xKarl98 Dec 6, 2025
c68bd50
simplify maybe_request_block_range and add comments
0xKarl98 Dec 6, 2025
19bdc02
chore: rerun CI
0xKarl98 Dec 7, 2025
39c725e
add eth/70 GetReceipts/Receipts message types and session wiring
0xKarl98 Dec 9, 2025
3e4e764
fix cargo doc
0xKarl98 Dec 9, 2025
b5ccdb8
remove unused import
0xKarl98 Dec 9, 2025
3b67d78
Add server-side support for eth/70 receipts pagination
0xKarl98 Dec 10, 2025
6d364b1
Reuse Receipts69 bloom helper for Receipts70
0xKarl98 Dec 10, 2025
2c70f51
remove block-range
0xKarl98 Dec 10, 2025
b7b3f32
address mattse suggestions
0xKarl98 Dec 11, 2025
d44a500
remove eth/70 block_range
0xKarl98 Dec 11, 2025
3f2f5ba
fix cargo doc
0xKarl98 Dec 11, 2025
c6628ae
fix test error
0xKarl98 Dec 11, 2025
f257508
remove unnessary last_range_update
0xKarl98 Dec 11, 2025
0b4d697
add manual encode for Receipts70
0xKarl98 Dec 11, 2025
d242059
apply mattese suggestions
0xKarl98 Dec 11, 2025
26db968
remove blockRangeUpdate from eth/70
0xKarl98 Dec 11, 2025
04d22f6
share eth/69 status with eth/70
0xKarl98 Dec 11, 2025
ed093cd
clean up
0xKarl98 Dec 11, 2025
35d214f
fix clippy
0xKarl98 Dec 11, 2025
b6d0e04
cargo fmt
0xKarl98 Dec 11, 2025
72bfdfe
remove unnessary macro
0xKarl98 Dec 11, 2025
fc8931c
remove unnessary macro
0xKarl98 Dec 11, 2025
5db0e51
restore max
0xKarl98 Dec 11, 2025
fd3b778
restore unnessary comment change
0xKarl98 Dec 11, 2025
6a4e5eb
remove unnessary import
0xKarl98 Dec 11, 2025
912ce28
cargo fmt
0xKarl98 Dec 11, 2025
3ab7774
restore unnessary session changes
0xKarl98 Dec 11, 2025
1af8a03
Delegating constraints to the method level
0xKarl98 Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
1 change: 1 addition & 0 deletions .dprint-cache/locks/.4365269138195617005.lock.poll
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
1 change: 1 addition & 0 deletions .dprint-cache/plugin-cache-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"schemaVersion":8,"wasmCacheVersion":"6.1.0-rc.3","plugins":{}}
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
2 changes: 1 addition & 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,7 @@
extern crate alloc;

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

pub mod version;
pub use version::{EthVersion, ProtocolVersion};
Expand Down
160 changes: 149 additions & 11 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 @@ -8,13 +8,13 @@

use super::{
broadcast::NewBlockHashes, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders,
GetNodeData, GetPooledTransactions, GetReceipts, NewPooledTransactionHashes66,
GetNodeData, GetPooledTransactions, GetReceipts, GetReceipts70, NewPooledTransactionHashes66,
NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, StatusEth69,
Transactions,
StatusEth70, Transactions,
};
use crate::{
status::StatusMessage, BlockRangeUpdate, EthNetworkPrimitives, EthVersion, NetworkPrimitives,
RawCapabilityMessage, Receipts69, SharedTransactions,
RawCapabilityMessage, Receipts69, Receipts70, SharedTransactions,
};
use alloc::{boxed::Box, string::String, sync::Arc};
use alloy_primitives::{
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 @@ -111,13 +113,24 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
}
EthMessage::NodeData(RequestPair::decode(buf)?)
}
EthMessageID::GetReceipts => EthMessage::GetReceipts(RequestPair::decode(buf)?),
EthMessageID::GetReceipts => {
if version >= EthVersion::Eth70 {
EthMessage::GetReceipts70(crate::receipts::GetReceipts70::decode(buf)?)
} else {
EthMessage::GetReceipts(RequestPair::decode(buf)?)
}
}
EthMessageID::Receipts => {
if version < EthVersion::Eth69 {
EthMessage::Receipts(RequestPair::decode(buf)?)
} else {
} else if version < EthVersion::Eth70 {
// with eth69, receipts no longer include the bloom
EthMessage::Receipts69(RequestPair::decode(buf)?)
} else {
// eth/70 continues to omit bloom filters and adds the
// `lastBlockIncomplete` flag, encoded as
// `[request-id, lastBlockIncomplete, [[receipt₁, receipt₂], ...]]`.
EthMessage::Receipts70(crate::receipts::Receipts70::<N::Receipt>::decode(buf)?)
}
}
EthMessageID::BlockRangeUpdate => {
Expand Down Expand Up @@ -205,6 +218,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) keeps the eth/69 status format and introduces partial receipts
/// requests/responses.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
Expand Down Expand Up @@ -259,6 +275,12 @@ pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
NodeData(RequestPair<NodeData>),
/// Represents a `GetReceipts` request-response pair.
GetReceipts(RequestPair<GetReceipts>),
/// Represents a `GetReceipts` request for eth/70.
///
/// Note: Unlike earlier protocol versions, the eth/70 encoding for
/// `GetReceipts` in EIP-7975 inlines the request id. The type still wraps
/// a [`RequestPair`], but with a custom inline encoding.
GetReceipts70(GetReceipts70),
/// Represents a Receipts request-response pair.
#[cfg_attr(
feature = "serde",
Expand All @@ -271,6 +293,16 @@ pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
serde(bound = "N::Receipt: serde::Serialize + serde::de::DeserializeOwned")
)]
Receipts69(RequestPair<Receipts69<N::Receipt>>),
/// Represents a Receipts request-response pair for eth/70.
#[cfg_attr(
feature = "serde",
serde(bound = "N::Receipt: serde::Serialize + serde::de::DeserializeOwned")
)]
///
/// Note: The eth/70 encoding for `Receipts` in EIP-7975 inlines the
/// request id. The type still wraps a [`RequestPair`], but with a custom
/// inline encoding.
Receipts70(Receipts70<N::Receipt>),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should also use RequestPair and we can implement encode manually for Receipts70

Copy link
Contributor Author

@0xKarl98 0xKarl98 Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have used RequestPair here , but the comment isn't clear , i.e. :
pub type Receipts70<T = Receipt> = crate::message::RequestPair<Receipts70Payload>;

Will make others misunderstood that we don't use RequestPair here but i wil simplify it

/// Represents a `BlockRangeUpdate` message broadcast to the network.
#[cfg_attr(
feature = "serde",
Expand Down Expand Up @@ -300,8 +332,8 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::PooledTransactions(_) => EthMessageID::PooledTransactions,
Self::GetNodeData(_) => EthMessageID::GetNodeData,
Self::NodeData(_) => EthMessageID::NodeData,
Self::GetReceipts(_) => EthMessageID::GetReceipts,
Self::Receipts(_) | Self::Receipts69(_) => EthMessageID::Receipts,
Self::GetReceipts(_) | Self::GetReceipts70(_) => EthMessageID::GetReceipts,
Self::Receipts(_) | Self::Receipts69(_) | Self::Receipts70(_) => EthMessageID::Receipts,
Self::BlockRangeUpdate(_) => EthMessageID::BlockRangeUpdate,
Self::Other(msg) => EthMessageID::Other(msg.id as u8),
}
Expand All @@ -314,6 +346,7 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::GetBlockBodies(_) |
Self::GetBlockHeaders(_) |
Self::GetReceipts(_) |
Self::GetReceipts70(_) |
Self::GetPooledTransactions(_) |
Self::GetNodeData(_)
)
Expand All @@ -326,6 +359,7 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::PooledTransactions(_) |
Self::Receipts(_) |
Self::Receipts69(_) |
Self::Receipts70(_) |
Self::BlockHeaders(_) |
Self::BlockBodies(_) |
Self::NodeData(_)
Expand All @@ -351,8 +385,10 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
Self::GetNodeData(request) => request.encode(out),
Self::NodeData(data) => data.encode(out),
Self::GetReceipts(request) => request.encode(out),
Self::GetReceipts70(request) => request.encode(out),
Self::Receipts(receipts) => receipts.encode(out),
Self::Receipts69(receipt69) => receipt69.encode(out),
Self::Receipts70(receipt70) => receipt70.encode(out),
Self::BlockRangeUpdate(block_range_update) => block_range_update.encode(out),
Self::Other(unknown) => out.put_slice(&unknown.payload),
}
Expand All @@ -374,8 +410,10 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
Self::GetNodeData(request) => request.length(),
Self::NodeData(data) => data.length(),
Self::GetReceipts(request) => request.length(),
Self::GetReceipts70(request) => request.length(),
Self::Receipts(receipts) => receipts.length(),
Self::Receipts69(receipt69) => receipt69.length(),
Self::Receipts70(receipt70) => receipt70.length(),
Self::BlockRangeUpdate(block_range_update) => block_range_update.length(),
Self::Other(unknown) => unknown.length(),
}
Expand Down Expand Up @@ -597,6 +635,106 @@ impl<T> RequestPair<T> {
}
}

impl RequestPair<crate::receipts::GetReceipts70Payload> {
/// Encodes the request id and payload in a flattened list:
/// `[request-id, firstBlockReceiptIndex, [blockhashes...]]`.
pub fn encode_inline(&self, out: &mut dyn alloy_rlp::BufMut) {
let payload_length = self.request_id.length() +
self.message.first_block_receipt_index.length() +
self.message.block_hashes.length();
let header = Header { list: true, payload_length };
header.encode(out);
self.request_id.encode(out);
self.message.first_block_receipt_index.encode(out);
self.message.block_hashes.encode(out);
}

/// Returns the length of the flattened encoding.
pub fn length_inline(&self) -> usize {
let mut length = 0;
length += self.request_id.length();
length += self.message.first_block_receipt_index.length();
length += self.message.block_hashes.length();
length += length_of_length(length);
length
}

/// Decodes the flattened eth/70 `GetReceipts` payload.
pub fn decode_inline(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header = Header::decode(buf)?;
let initial_length = buf.len();
let request_id = u64::decode(buf)?;
let first_block_receipt_index = u64::decode(buf)?;
let block_hashes = Vec::<alloy_primitives::B256>::decode(buf)?;

let consumed_len = initial_length - buf.len();
if consumed_len != header.payload_length {
return Err(alloy_rlp::Error::UnexpectedLength)
}

Ok(Self {
request_id,
message: crate::receipts::GetReceipts70Payload {
first_block_receipt_index,
block_hashes,
},
})
}
}

impl<T> RequestPair<crate::receipts::Receipts70Payload<T>> {
/// Encodes the request id and payload in a flattened list:
/// `[request-id, lastBlockIncomplete, [[receipts...], ...]]`.
pub fn encode_inline(&self, out: &mut dyn alloy_rlp::BufMut)
where
T: Encodable,
{
let payload_length = self.request_id.length() +
self.message.last_block_incomplete.length() +
self.message.receipts.length();
let header = Header { list: true, payload_length };
header.encode(out);
self.request_id.encode(out);
self.message.last_block_incomplete.encode(out);
self.message.receipts.encode(out);
}

/// Returns the length of the flattened encoding.
pub fn length_inline(&self) -> usize
where
T: Encodable,
{
let mut length = 0;
length += self.request_id.length();
length += self.message.last_block_incomplete.length();
length += self.message.receipts.length();
length += length_of_length(length);
length
}

/// Decodes the flattened eth/70 Receipts payload.
pub fn decode_inline(buf: &mut &[u8]) -> alloy_rlp::Result<Self>
where
T: Decodable,
{
let header = Header::decode(buf)?;
let initial_length = buf.len();
let request_id = u64::decode(buf)?;
let last_block_incomplete = bool::decode(buf)?;
let receipts = Vec::<Vec<T>>::decode(buf)?;

let consumed_len = initial_length - buf.len();
if consumed_len != header.payload_length {
return Err(alloy_rlp::Error::UnexpectedLength)
}

Ok(Self {
request_id,
message: crate::receipts::Receipts70Payload { last_block_incomplete, receipts },
})
}
}

/// Allows messages with request ids to be serialized into RLP bytes.
impl<T> Encodable for RequestPair<T>
where
Expand Down
Loading
Loading