feat: add support for eth/70 eip-7975 (#20255)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Karl Yu
2025-12-16 20:05:11 +08:00
committed by GitHub
parent ad63b135d6
commit d72935628a
16 changed files with 487 additions and 36 deletions

View File

@@ -169,7 +169,7 @@ impl NewPooledTransactionHashes {
matches!(version, EthVersion::Eth67 | EthVersion::Eth66) matches!(version, EthVersion::Eth67 | EthVersion::Eth66)
} }
Self::Eth68(_) => { Self::Eth68(_) => {
matches!(version, EthVersion::Eth68 | EthVersion::Eth69) matches!(version, EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70)
} }
} }
} }

View File

@@ -100,6 +100,16 @@ impl Capability {
Self::eth(EthVersion::Eth68) 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. /// Whether this is eth v66 protocol.
#[inline] #[inline]
pub fn is_eth_v66(&self) -> bool { pub fn is_eth_v66(&self) -> bool {
@@ -118,10 +128,26 @@ impl Capability {
self.name == "eth" && self.version == 68 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. /// Whether this is any eth version.
#[inline] #[inline]
pub fn is_eth(&self) -> bool { 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()
} }
} }
@@ -141,7 +167,7 @@ impl From<EthVersion> for Capability {
#[cfg(any(test, feature = "arbitrary"))] #[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Capability { impl<'a> arbitrary::Arbitrary<'a> for Capability {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { 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 // Only generate valid eth protocol name for now since it's the only supported protocol
Ok(Self::new_static("eth", version)) Ok(Self::new_static("eth", version))
} }
@@ -155,6 +181,8 @@ pub struct Capabilities {
eth_66: bool, eth_66: bool,
eth_67: bool, eth_67: bool,
eth_68: bool, eth_68: bool,
eth_69: bool,
eth_70: bool,
} }
impl Capabilities { impl Capabilities {
@@ -164,6 +192,8 @@ impl Capabilities {
eth_66: value.iter().any(Capability::is_eth_v66), eth_66: value.iter().any(Capability::is_eth_v66),
eth_67: value.iter().any(Capability::is_eth_v67), eth_67: value.iter().any(Capability::is_eth_v67),
eth_68: value.iter().any(Capability::is_eth_v68), 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, inner: value,
} }
} }
@@ -182,7 +212,7 @@ impl Capabilities {
/// Whether the peer supports `eth` sub-protocol. /// Whether the peer supports `eth` sub-protocol.
#[inline] #[inline]
pub const fn supports_eth(&self) -> bool { 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. /// Whether this peer supports eth v66 protocol.
@@ -202,6 +232,18 @@ impl Capabilities {
pub const fn supports_eth_v68(&self) -> bool { pub const fn supports_eth_v68(&self) -> bool {
self.eth_68 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 { impl From<Vec<Capability>> for Capabilities {
@@ -224,6 +266,8 @@ impl Decodable for Capabilities {
eth_66: inner.iter().any(Capability::is_eth_v66), eth_66: inner.iter().any(Capability::is_eth_v66),
eth_67: inner.iter().any(Capability::is_eth_v67), eth_67: inner.iter().any(Capability::is_eth_v67),
eth_68: inner.iter().any(Capability::is_eth_v68), 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, inner,
}) })
} }

View File

@@ -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. //! Defines structs/enums for messages, request-response pairs, and broadcasts.
//! Handles compatibility with [`EthVersion`]. //! Handles compatibility with [`EthVersion`].
//! //!
@@ -8,13 +8,13 @@
use super::{ use super::{
broadcast::NewBlockHashes, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders, broadcast::NewBlockHashes, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders,
GetNodeData, GetPooledTransactions, GetReceipts, NewPooledTransactionHashes66, GetNodeData, GetPooledTransactions, GetReceipts, GetReceipts70, NewPooledTransactionHashes66,
NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, StatusEth69, NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, StatusEth69,
Transactions, Transactions,
}; };
use crate::{ use crate::{
status::StatusMessage, BlockRangeUpdate, EthNetworkPrimitives, EthVersion, NetworkPrimitives, status::StatusMessage, BlockRangeUpdate, EthNetworkPrimitives, EthVersion, NetworkPrimitives,
RawCapabilityMessage, Receipts69, SharedTransactions, RawCapabilityMessage, Receipts69, Receipts70, SharedTransactions,
}; };
use alloc::{boxed::Box, string::String, sync::Arc}; use alloc::{boxed::Box, string::String, sync::Arc};
use alloy_primitives::{ use alloy_primitives::{
@@ -111,13 +111,29 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
} }
EthMessage::NodeData(RequestPair::decode(buf)?) EthMessage::NodeData(RequestPair::decode(buf)?)
} }
EthMessageID::GetReceipts => EthMessage::GetReceipts(RequestPair::decode(buf)?), EthMessageID::GetReceipts => {
EthMessageID::Receipts => { if version >= EthVersion::Eth70 {
if version < EthVersion::Eth69 { EthMessage::GetReceipts70(RequestPair::decode(buf)?)
EthMessage::Receipts(RequestPair::decode(buf)?)
} else { } else {
// with eth69, receipts no longer include the bloom EthMessage::GetReceipts(RequestPair::decode(buf)?)
EthMessage::Receipts69(RequestPair::decode(buf)?) }
}
EthMessageID::Receipts => {
match version {
v if v >= EthVersion::Eth70 => {
// eth/70 continues to omit bloom filters and adds the
// `lastBlockIncomplete` flag, encoded as
// `[request-id, lastBlockIncomplete, [[receipt₁, receipt₂], ...]]`.
EthMessage::Receipts70(RequestPair::decode(buf)?)
}
EthVersion::Eth69 => {
// with eth69, receipts no longer include the bloom
EthMessage::Receipts69(RequestPair::decode(buf)?)
}
_ => {
// before eth69 we need to decode the bloom as well
EthMessage::Receipts(RequestPair::decode(buf)?)
}
} }
} }
EthMessageID::BlockRangeUpdate => { EthMessageID::BlockRangeUpdate => {
@@ -205,6 +221,9 @@ impl<N: NetworkPrimitives> From<EthBroadcastMessage<N>> for ProtocolBroadcastMes
/// ///
/// The `eth/69` announces the historical block range served by the node. Removes total difficulty /// 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. /// 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)] #[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> { pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
@@ -259,6 +278,12 @@ pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
NodeData(RequestPair<NodeData>), NodeData(RequestPair<NodeData>),
/// Represents a `GetReceipts` request-response pair. /// Represents a `GetReceipts` request-response pair.
GetReceipts(RequestPair<GetReceipts>), 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(RequestPair<GetReceipts70>),
/// Represents a Receipts request-response pair. /// Represents a Receipts request-response pair.
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
@@ -271,6 +296,16 @@ pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
serde(bound = "N::Receipt: serde::Serialize + serde::de::DeserializeOwned") serde(bound = "N::Receipt: serde::Serialize + serde::de::DeserializeOwned")
)] )]
Receipts69(RequestPair<Receipts69<N::Receipt>>), 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(RequestPair<Receipts70<N::Receipt>>),
/// Represents a `BlockRangeUpdate` message broadcast to the network. /// Represents a `BlockRangeUpdate` message broadcast to the network.
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
@@ -300,8 +335,8 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::PooledTransactions(_) => EthMessageID::PooledTransactions, Self::PooledTransactions(_) => EthMessageID::PooledTransactions,
Self::GetNodeData(_) => EthMessageID::GetNodeData, Self::GetNodeData(_) => EthMessageID::GetNodeData,
Self::NodeData(_) => EthMessageID::NodeData, Self::NodeData(_) => EthMessageID::NodeData,
Self::GetReceipts(_) => EthMessageID::GetReceipts, Self::GetReceipts(_) | Self::GetReceipts70(_) => EthMessageID::GetReceipts,
Self::Receipts(_) | Self::Receipts69(_) => EthMessageID::Receipts, Self::Receipts(_) | Self::Receipts69(_) | Self::Receipts70(_) => EthMessageID::Receipts,
Self::BlockRangeUpdate(_) => EthMessageID::BlockRangeUpdate, Self::BlockRangeUpdate(_) => EthMessageID::BlockRangeUpdate,
Self::Other(msg) => EthMessageID::Other(msg.id as u8), Self::Other(msg) => EthMessageID::Other(msg.id as u8),
} }
@@ -314,6 +349,7 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::GetBlockBodies(_) | Self::GetBlockBodies(_) |
Self::GetBlockHeaders(_) | Self::GetBlockHeaders(_) |
Self::GetReceipts(_) | Self::GetReceipts(_) |
Self::GetReceipts70(_) |
Self::GetPooledTransactions(_) | Self::GetPooledTransactions(_) |
Self::GetNodeData(_) Self::GetNodeData(_)
) )
@@ -326,11 +362,40 @@ impl<N: NetworkPrimitives> EthMessage<N> {
Self::PooledTransactions(_) | Self::PooledTransactions(_) |
Self::Receipts(_) | Self::Receipts(_) |
Self::Receipts69(_) | Self::Receipts69(_) |
Self::Receipts70(_) |
Self::BlockHeaders(_) | Self::BlockHeaders(_) |
Self::BlockBodies(_) | Self::BlockBodies(_) |
Self::NodeData(_) Self::NodeData(_)
) )
} }
/// Converts the message types where applicable.
///
/// This handles up/downcasting where appropriate, for example for different receipt request
/// types.
pub fn map_versioned(self, version: EthVersion) -> Self {
// For eth/70 peers we send `GetReceipts` using the new eth/70
// encoding with `firstBlockReceiptIndex = 0`, while keeping the
// user-facing `PeerRequest` API unchanged.
if version >= EthVersion::Eth70 {
return match self {
Self::GetReceipts(pair) => {
let RequestPair { request_id, message } = pair;
let req = RequestPair {
request_id,
message: GetReceipts70 {
first_block_receipt_index: 0,
block_hashes: message.0,
},
};
Self::GetReceipts70(req)
}
other => other,
}
}
self
}
} }
impl<N: NetworkPrimitives> Encodable for EthMessage<N> { impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
@@ -351,8 +416,10 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
Self::GetNodeData(request) => request.encode(out), Self::GetNodeData(request) => request.encode(out),
Self::NodeData(data) => data.encode(out), Self::NodeData(data) => data.encode(out),
Self::GetReceipts(request) => request.encode(out), Self::GetReceipts(request) => request.encode(out),
Self::GetReceipts70(request) => request.encode(out),
Self::Receipts(receipts) => receipts.encode(out), Self::Receipts(receipts) => receipts.encode(out),
Self::Receipts69(receipt69) => receipt69.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::BlockRangeUpdate(block_range_update) => block_range_update.encode(out),
Self::Other(unknown) => out.put_slice(&unknown.payload), Self::Other(unknown) => out.put_slice(&unknown.payload),
} }
@@ -374,8 +441,10 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
Self::GetNodeData(request) => request.length(), Self::GetNodeData(request) => request.length(),
Self::NodeData(data) => data.length(), Self::NodeData(data) => data.length(),
Self::GetReceipts(request) => request.length(), Self::GetReceipts(request) => request.length(),
Self::GetReceipts70(request) => request.length(),
Self::Receipts(receipts) => receipts.length(), Self::Receipts(receipts) => receipts.length(),
Self::Receipts69(receipt69) => receipt69.length(), Self::Receipts69(receipt69) => receipt69.length(),
Self::Receipts70(receipt70) => receipt70.length(),
Self::BlockRangeUpdate(block_range_update) => block_range_update.length(), Self::BlockRangeUpdate(block_range_update) => block_range_update.length(),
Self::Other(unknown) => unknown.length(), Self::Other(unknown) => unknown.length(),
} }

View File

@@ -17,6 +17,42 @@ pub struct GetReceipts(
pub Vec<B256>, pub Vec<B256>,
); );
/// Eth/70 `GetReceipts` request payload that supports partial receipt queries.
///
/// When used with eth/70, the request id is carried by the surrounding
/// [`crate::message::RequestPair`], and the on-wire shape is the flattened list
/// `firstBlockReceiptIndex, [blockhash₁, ...]`.
///
/// See also [eip-7975](https://eips.ethereum.org/EIPS/eip-7975)
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub struct GetReceipts70 {
/// Index into the receipts of the first requested block hash.
pub first_block_receipt_index: u64,
/// The block hashes to request receipts for.
pub block_hashes: Vec<B256>,
}
impl alloy_rlp::Encodable for GetReceipts70 {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.first_block_receipt_index.encode(out);
self.block_hashes.encode(out);
}
fn length(&self) -> usize {
self.first_block_receipt_index.length() + self.block_hashes.length()
}
}
impl alloy_rlp::Decodable for GetReceipts70 {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let first_block_receipt_index = u64::decode(buf)?;
let block_hashes = Vec::<B256>::decode(buf)?;
Ok(Self { first_block_receipt_index, block_hashes })
}
}
/// The response to [`GetReceipts`], containing receipt lists that correspond to each block /// The response to [`GetReceipts`], containing receipt lists that correspond to each block
/// requested. /// requested.
#[derive(Clone, Debug, PartialEq, Eq, Default)] #[derive(Clone, Debug, PartialEq, Eq, Default)]
@@ -58,7 +94,13 @@ pub struct Receipts69<T = Receipt>(pub Vec<Vec<T>>);
impl<T: TxReceipt> Receipts69<T> { impl<T: TxReceipt> Receipts69<T> {
/// Encodes all receipts with the bloom filter. /// Encodes all receipts with the bloom filter.
/// ///
/// Note: This is an expensive operation that recalculates the bloom for each receipt. /// Eth/69 omits bloom filters on the wire, while some internal callers
/// (and legacy APIs) still operate on [`Receipts`] with
/// [`ReceiptWithBloom`]. This helper reconstructs the bloom locally from
/// each receipt's logs so the older API can be used on top of eth/69 data.
///
/// Note: This is an expensive operation that recalculates the bloom for
/// every receipt.
pub fn into_with_bloom(self) -> Receipts<T> { pub fn into_with_bloom(self) -> Receipts<T> {
Receipts( Receipts(
self.0 self.0
@@ -75,6 +117,68 @@ impl<T: TxReceipt> From<Receipts69<T>> for Receipts<T> {
} }
} }
/// Eth/70 `Receipts` response payload.
///
/// This is used in conjunction with [`crate::message::RequestPair`] to encode the full wire
/// message `[request-id, lastBlockIncomplete, [[receipt₁, receipt₂], ...]]`.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub struct Receipts70<T = Receipt> {
/// Whether the receipts list for the last block is incomplete.
pub last_block_incomplete: bool,
/// Receipts grouped by block.
pub receipts: Vec<Vec<T>>,
}
impl<T> alloy_rlp::Encodable for Receipts70<T>
where
T: alloy_rlp::Encodable,
{
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.last_block_incomplete.encode(out);
self.receipts.encode(out);
}
fn length(&self) -> usize {
self.last_block_incomplete.length() + self.receipts.length()
}
}
impl<T> alloy_rlp::Decodable for Receipts70<T>
where
T: alloy_rlp::Decodable,
{
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let last_block_incomplete = bool::decode(buf)?;
let receipts = Vec::<Vec<T>>::decode(buf)?;
Ok(Self { last_block_incomplete, receipts })
}
}
impl<T: TxReceipt> Receipts70<T> {
/// Encodes all receipts with the bloom filter.
///
/// Just like eth/69, eth/70 does not transmit bloom filters over the wire.
/// When higher layers still expect the older bloom-bearing [`Receipts`]
/// type, this helper converts the eth/70 payload into that shape by
/// recomputing the bloom locally from the contained receipts.
///
/// Note: This is an expensive operation that recalculates the bloom for
/// every receipt.
pub fn into_with_bloom(self) -> Receipts<T> {
// Reuse the eth/69 helper, since both variants carry the same
// receipt list shape (only eth/70 adds request metadata).
Receipts69(self.receipts).into_with_bloom()
}
}
impl<T: TxReceipt> From<Receipts70<T>> for Receipts<T> {
fn from(receipts: Receipts70<T>) -> Self {
receipts.into_with_bloom()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -225,4 +329,70 @@ mod tests {
let encoded = alloy_rlp::encode(&request); let encoded = alloy_rlp::encode(&request);
assert_eq!(encoded, data); assert_eq!(encoded, data);
} }
#[test]
fn encode_get_receipts70_inline_shape() {
let req = RequestPair {
request_id: 1111,
message: GetReceipts70 {
first_block_receipt_index: 0,
block_hashes: vec![
hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
],
},
};
let mut out = vec![];
req.encode(&mut out);
let mut buf = out.as_slice();
let header = alloy_rlp::Header::decode(&mut buf).unwrap();
let payload_start = buf.len();
let request_id = u64::decode(&mut buf).unwrap();
let first_block_receipt_index = u64::decode(&mut buf).unwrap();
let block_hashes = Vec::<B256>::decode(&mut buf).unwrap();
assert!(buf.is_empty(), "buffer not fully consumed");
assert_eq!(request_id, 1111);
assert_eq!(first_block_receipt_index, 0);
assert_eq!(block_hashes.len(), 2);
// ensure payload length matches header
assert_eq!(payload_start - buf.len(), header.payload_length);
let mut buf = out.as_slice();
let decoded = RequestPair::<GetReceipts70>::decode(&mut buf).unwrap();
assert!(buf.is_empty(), "buffer not fully consumed on decode");
assert_eq!(decoded, req);
}
#[test]
fn encode_receipts70_inline_shape() {
let payload: Receipts70<Receipt> =
Receipts70 { last_block_incomplete: true, receipts: vec![vec![Receipt::default()]] };
let resp = RequestPair { request_id: 7, message: payload };
let mut out = vec![];
resp.encode(&mut out);
let mut buf = out.as_slice();
let header = alloy_rlp::Header::decode(&mut buf).unwrap();
let payload_start = buf.len();
let request_id = u64::decode(&mut buf).unwrap();
let last_block_incomplete = bool::decode(&mut buf).unwrap();
let receipts = Vec::<Vec<Receipt>>::decode(&mut buf).unwrap();
assert!(buf.is_empty(), "buffer not fully consumed");
assert_eq!(payload_start - buf.len(), header.payload_length);
assert_eq!(request_id, 7);
assert!(last_block_incomplete);
assert_eq!(receipts.len(), 1);
assert_eq!(receipts[0].len(), 1);
let mut buf = out.as_slice();
let decoded = RequestPair::<Receipts70>::decode(&mut buf).unwrap();
assert!(buf.is_empty(), "buffer not fully consumed on decode");
assert_eq!(decoded, resp);
}
} }

View File

@@ -13,7 +13,7 @@ use reth_codecs_derive::add_arbitrary_tests;
/// unsupported fields are stripped out. /// unsupported fields are stripped out.
#[derive(Clone, Debug, PartialEq, Eq, Copy)] #[derive(Clone, Debug, PartialEq, Eq, Copy)]
pub struct UnifiedStatus { pub struct UnifiedStatus {
/// The eth protocol version (e.g. eth/66 to eth/69). /// The eth protocol version (e.g. eth/66 to eth/70).
pub version: EthVersion, pub version: EthVersion,
/// The chain ID identifying the peers network. /// The chain ID identifying the peers network.
pub chain: Chain, pub chain: Chain,
@@ -157,7 +157,7 @@ impl StatusBuilder {
self.status self.status
} }
/// Sets the eth protocol version (e.g., eth/66, eth/69). /// Sets the eth protocol version (e.g., eth/66, eth/70).
pub const fn version(mut self, version: EthVersion) -> Self { pub const fn version(mut self, version: EthVersion) -> Self {
self.status.version = version; self.status.version = version;
self self
@@ -378,8 +378,8 @@ impl Debug for StatusEth69 {
} }
} }
/// `StatusMessage` can store either the Legacy version (with TD) or the /// `StatusMessage` can store either the Legacy version (with TD), or the eth/69+/eth/70 version
/// eth/69 version (omits TD). /// (omits TD, includes block range).
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StatusMessage { pub enum StatusMessage {
@@ -546,6 +546,24 @@ mod tests {
assert_eq!(unified_status, roundtripped_unified_status); assert_eq!(unified_status, roundtripped_unified_status);
} }
#[test]
fn roundtrip_eth70() {
let unified_status = UnifiedStatus::builder()
.version(EthVersion::Eth70)
.chain(Chain::mainnet())
.genesis(MAINNET_GENESIS_HASH)
.forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
.blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
.total_difficulty(None)
.earliest_block(Some(1))
.latest_block(Some(2))
.build();
let status_message = unified_status.into_message();
let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
assert_eq!(unified_status, roundtripped_unified_status);
}
#[test] #[test]
fn encode_eth69_status_message() { fn encode_eth69_status_message() {
let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"); let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");

View File

@@ -27,6 +27,8 @@ pub enum EthVersion {
Eth68 = 68, Eth68 = 68,
/// The `eth` protocol version 69. /// The `eth` protocol version 69.
Eth69 = 69, Eth69 = 69,
/// The `eth` protocol version 70.
Eth70 = 70,
} }
impl EthVersion { impl EthVersion {
@@ -55,6 +57,11 @@ impl EthVersion {
pub const fn is_eth69(&self) -> bool { pub const fn is_eth69(&self) -> bool {
matches!(self, Self::Eth69) matches!(self, Self::Eth69)
} }
/// Returns true if the version is eth/70
pub const fn is_eth70(&self) -> bool {
matches!(self, Self::Eth70)
}
} }
/// RLP encodes `EthVersion` as a single byte (66-69). /// RLP encodes `EthVersion` as a single byte (66-69).
@@ -96,6 +103,7 @@ impl TryFrom<&str> for EthVersion {
"67" => Ok(Self::Eth67), "67" => Ok(Self::Eth67),
"68" => Ok(Self::Eth68), "68" => Ok(Self::Eth68),
"69" => Ok(Self::Eth69), "69" => Ok(Self::Eth69),
"70" => Ok(Self::Eth70),
_ => Err(ParseVersionError(s.to_string())), _ => Err(ParseVersionError(s.to_string())),
} }
} }
@@ -120,6 +128,7 @@ impl TryFrom<u8> for EthVersion {
67 => Ok(Self::Eth67), 67 => Ok(Self::Eth67),
68 => Ok(Self::Eth68), 68 => Ok(Self::Eth68),
69 => Ok(Self::Eth69), 69 => Ok(Self::Eth69),
70 => Ok(Self::Eth70),
_ => Err(ParseVersionError(u.to_string())), _ => Err(ParseVersionError(u.to_string())),
} }
} }
@@ -149,6 +158,7 @@ impl From<EthVersion> for &'static str {
EthVersion::Eth67 => "67", EthVersion::Eth67 => "67",
EthVersion::Eth68 => "68", EthVersion::Eth68 => "68",
EthVersion::Eth69 => "69", EthVersion::Eth69 => "69",
EthVersion::Eth70 => "70",
} }
} }
} }
@@ -195,7 +205,7 @@ impl Decodable for ProtocolVersion {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{EthVersion, ParseVersionError}; use super::EthVersion;
use alloy_rlp::{Decodable, Encodable, Error as RlpError}; use alloy_rlp::{Decodable, Encodable, Error as RlpError};
use bytes::BytesMut; use bytes::BytesMut;
@@ -205,7 +215,7 @@ mod tests {
assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap()); assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap());
assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap()); assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap());
assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap()); assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap());
assert_eq!(Err(ParseVersionError("70".to_string())), EthVersion::try_from("70")); assert_eq!(EthVersion::Eth70, EthVersion::try_from("70").unwrap());
} }
#[test] #[test]
@@ -214,12 +224,18 @@ mod tests {
assert_eq!(EthVersion::Eth67, "67".parse().unwrap()); assert_eq!(EthVersion::Eth67, "67".parse().unwrap());
assert_eq!(EthVersion::Eth68, "68".parse().unwrap()); assert_eq!(EthVersion::Eth68, "68".parse().unwrap());
assert_eq!(EthVersion::Eth69, "69".parse().unwrap()); assert_eq!(EthVersion::Eth69, "69".parse().unwrap());
assert_eq!(Err(ParseVersionError("70".to_string())), "70".parse::<EthVersion>()); assert_eq!(EthVersion::Eth70, "70".parse().unwrap());
} }
#[test] #[test]
fn test_eth_version_rlp_encode() { fn test_eth_version_rlp_encode() {
let versions = [EthVersion::Eth66, EthVersion::Eth67, EthVersion::Eth68, EthVersion::Eth69]; let versions = [
EthVersion::Eth66,
EthVersion::Eth67,
EthVersion::Eth68,
EthVersion::Eth69,
EthVersion::Eth70,
];
for version in versions { for version in versions {
let mut encoded = BytesMut::new(); let mut encoded = BytesMut::new();
@@ -236,7 +252,7 @@ mod tests {
(67_u8, Ok(EthVersion::Eth67)), (67_u8, Ok(EthVersion::Eth67)),
(68_u8, Ok(EthVersion::Eth68)), (68_u8, Ok(EthVersion::Eth68)),
(69_u8, Ok(EthVersion::Eth69)), (69_u8, Ok(EthVersion::Eth69)),
(70_u8, Err(RlpError::Custom("invalid eth version"))), (70_u8, Ok(EthVersion::Eth70)),
(65_u8, Err(RlpError::Custom("invalid eth version"))), (65_u8, Err(RlpError::Custom("invalid eth version"))),
]; ];

View File

@@ -418,6 +418,8 @@ mod tests {
Capability::new_static("eth", 66), Capability::new_static("eth", 66),
Capability::new_static("eth", 67), Capability::new_static("eth", 67),
Capability::new_static("eth", 68), Capability::new_static("eth", 68),
Capability::new_static("eth", 69),
Capability::new_static("eth", 70),
] ]
.into(); .into();
@@ -425,6 +427,8 @@ mod tests {
assert!(capabilities.supports_eth_v66()); assert!(capabilities.supports_eth_v66());
assert!(capabilities.supports_eth_v67()); assert!(capabilities.supports_eth_v67());
assert!(capabilities.supports_eth_v68()); assert!(capabilities.supports_eth_v68());
assert!(capabilities.supports_eth_v69());
assert!(capabilities.supports_eth_v70());
} }
#[test] #[test]

View File

@@ -260,10 +260,11 @@ mod tests {
assert_eq!(hello_encoded.len(), hello.length()); assert_eq!(hello_encoded.len(), hello.length());
} }
//TODO: add test for eth70 here once we have fully support it
#[test] #[test]
fn test_default_protocols_include_eth69() { fn test_default_protocols_still_include_eth69() {
// ensure that the default protocol list includes Eth69 as the latest version // ensure that older eth/69 remains advertised for compatibility
let secret_key = SecretKey::new(&mut rand_08::thread_rng()); let secret_key = SecretKey::new(&mut rand_08::thread_rng());
let id = pk2id(&secret_key.public_key(SECP256K1)); let id = pk2id(&secret_key.public_key(SECP256K1));
let hello = HelloMessageWithProtocols::builder(id).build(); let hello = HelloMessageWithProtocols::builder(id).build();

View File

@@ -3,8 +3,8 @@
use reth_eth_wire_types::{ use reth_eth_wire_types::{
message::RequestPair, BlockBodies, BlockHeaders, Capabilities, DisconnectReason, EthMessage, message::RequestPair, BlockBodies, BlockHeaders, Capabilities, DisconnectReason, EthMessage,
EthNetworkPrimitives, EthVersion, GetBlockBodies, GetBlockHeaders, GetNodeData, EthNetworkPrimitives, EthVersion, GetBlockBodies, GetBlockHeaders, GetNodeData,
GetPooledTransactions, GetReceipts, NetworkPrimitives, NodeData, PooledTransactions, Receipts, GetPooledTransactions, GetReceipts, GetReceipts70, NetworkPrimitives, NodeData,
Receipts69, UnifiedStatus, PooledTransactions, Receipts, Receipts69, Receipts70, UnifiedStatus,
}; };
use reth_ethereum_forks::ForkId; use reth_ethereum_forks::ForkId;
use reth_network_p2p::error::{RequestError, RequestResult}; use reth_network_p2p::error::{RequestError, RequestResult};
@@ -238,6 +238,15 @@ pub enum PeerRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
/// The channel to send the response for receipts. /// The channel to send the response for receipts.
response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>, response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
}, },
/// Requests receipts from the peer using eth/70 (supports `firstBlockReceiptIndex`).
///
/// The response should be sent through the channel.
GetReceipts70 {
/// The request for receipts.
request: GetReceipts70,
/// The channel to send the response for receipts.
response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
},
} }
// === impl PeerRequest === // === impl PeerRequest ===
@@ -257,6 +266,7 @@ impl<N: NetworkPrimitives> PeerRequest<N> {
Self::GetNodeData { response, .. } => response.send(Err(err)).ok(), Self::GetNodeData { response, .. } => response.send(Err(err)).ok(),
Self::GetReceipts { response, .. } => response.send(Err(err)).ok(), Self::GetReceipts { response, .. } => response.send(Err(err)).ok(),
Self::GetReceipts69 { response, .. } => response.send(Err(err)).ok(), Self::GetReceipts69 { response, .. } => response.send(Err(err)).ok(),
Self::GetReceipts70 { response, .. } => response.send(Err(err)).ok(),
}; };
} }
@@ -281,6 +291,9 @@ impl<N: NetworkPrimitives> PeerRequest<N> {
Self::GetReceipts { request, .. } | Self::GetReceipts69 { request, .. } => { Self::GetReceipts { request, .. } | Self::GetReceipts69 { request, .. } => {
EthMessage::GetReceipts(RequestPair { request_id, message: request.clone() }) EthMessage::GetReceipts(RequestPair { request_id, message: request.clone() })
} }
Self::GetReceipts70 { request, .. } => {
EthMessage::GetReceipts70(RequestPair { request_id, message: request.clone() })
}
} }
} }

View File

@@ -10,7 +10,8 @@ use alloy_rlp::Encodable;
use futures::StreamExt; use futures::StreamExt;
use reth_eth_wire::{ use reth_eth_wire::{
BlockBodies, BlockHeaders, EthNetworkPrimitives, GetBlockBodies, GetBlockHeaders, GetNodeData, BlockBodies, BlockHeaders, EthNetworkPrimitives, GetBlockBodies, GetBlockHeaders, GetNodeData,
GetReceipts, HeadersDirection, NetworkPrimitives, NodeData, Receipts, Receipts69, GetReceipts, GetReceipts70, HeadersDirection, NetworkPrimitives, NodeData, Receipts,
Receipts69, Receipts70,
}; };
use reth_network_api::test_utils::PeersHandle; use reth_network_api::test_utils::PeersHandle;
use reth_network_p2p::error::RequestResult; use reth_network_p2p::error::RequestResult;
@@ -217,6 +218,69 @@ where
let _ = response.send(Ok(Receipts69(receipts))); let _ = response.send(Ok(Receipts69(receipts)));
} }
/// Handles partial responses for [`GetReceipts70`] queries.
///
/// This will adhere to the soft limit but allow filling the last vec partially.
fn on_receipts70_request(
&self,
_peer_id: PeerId,
request: GetReceipts70,
response: oneshot::Sender<RequestResult<Receipts70<C::Receipt>>>,
) {
self.metrics.eth_receipts_requests_received_total.increment(1);
let GetReceipts70 { first_block_receipt_index, block_hashes } = request;
let mut receipts = Vec::new();
let mut total_bytes = 0usize;
let mut last_block_incomplete = false;
for (idx, hash) in block_hashes.into_iter().enumerate() {
if idx >= MAX_RECEIPTS_SERVE {
break
}
let Some(mut block_receipts) =
self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
else {
break
};
if idx == 0 && first_block_receipt_index > 0 {
let skip = first_block_receipt_index as usize;
if skip >= block_receipts.len() {
block_receipts.clear();
} else {
block_receipts.drain(0..skip);
}
}
let block_size = block_receipts.length();
if total_bytes + block_size <= SOFT_RESPONSE_LIMIT {
total_bytes += block_size;
receipts.push(block_receipts);
continue;
}
let mut partial_block = Vec::new();
for receipt in block_receipts {
let receipt_size = receipt.length();
if total_bytes + receipt_size > SOFT_RESPONSE_LIMIT {
break;
}
total_bytes += receipt_size;
partial_block.push(receipt);
}
receipts.push(partial_block);
last_block_incomplete = true;
break;
}
let _ = response.send(Ok(Receipts70 { last_block_incomplete, receipts }));
}
#[inline] #[inline]
fn get_receipts_response<T, F>(&self, request: GetReceipts, transform_fn: F) -> Vec<Vec<T>> fn get_receipts_response<T, F>(&self, request: GetReceipts, transform_fn: F) -> Vec<Vec<T>>
where where
@@ -285,6 +349,9 @@ where
IncomingEthRequest::GetReceipts69 { peer_id, request, response } => { IncomingEthRequest::GetReceipts69 { peer_id, request, response } => {
this.on_receipts69_request(peer_id, request, response) this.on_receipts69_request(peer_id, request, response)
} }
IncomingEthRequest::GetReceipts70 { peer_id, request, response } => {
this.on_receipts70_request(peer_id, request, response)
}
} }
}, },
); );
@@ -359,4 +426,15 @@ pub enum IncomingEthRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
/// The channel sender for the response containing Receipts69. /// The channel sender for the response containing Receipts69.
response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>, response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
}, },
/// Request Receipts from the peer using eth/70.
///
/// The response should be sent through the channel.
GetReceipts70 {
/// The ID of the peer to request receipts from.
peer_id: PeerId,
/// The specific receipts requested including the `firstBlockReceiptIndex`.
request: GetReceipts70,
/// The channel sender for the response containing Receipts70.
response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
},
} }

View File

@@ -532,6 +532,13 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
response, response,
}) })
} }
PeerRequest::GetReceipts70 { request, response } => {
self.delegate_eth_request(IncomingEthRequest::GetReceipts70 {
peer_id,
request,
response,
})
}
PeerRequest::GetPooledTransactions { request, response } => { PeerRequest::GetPooledTransactions { request, response } => {
self.notify_tx_manager(NetworkTransactionEvent::GetPooledTransactions { self.notify_tx_manager(NetworkTransactionEvent::GetPooledTransactions {
peer_id, peer_id,

View File

@@ -3,7 +3,7 @@
//! An `RLPx` stream is multiplexed via the prepended message-id of a framed message. //! An `RLPx` stream is multiplexed via the prepended message-id of a framed message.
//! Capabilities are exchanged via the `RLPx` `Hello` message as pairs of `(id, version)`, <https://github.com/ethereum/devp2p/blob/master/rlpx.md#capability-messaging> //! Capabilities are exchanged via the `RLPx` `Hello` message as pairs of `(id, version)`, <https://github.com/ethereum/devp2p/blob/master/rlpx.md#capability-messaging>
use crate::types::Receipts69; use crate::types::{Receipts69, Receipts70};
use alloy_consensus::{BlockHeader, ReceiptWithBloom}; use alloy_consensus::{BlockHeader, ReceiptWithBloom};
use alloy_primitives::{Bytes, B256}; use alloy_primitives::{Bytes, B256};
use futures::FutureExt; use futures::FutureExt;
@@ -116,6 +116,11 @@ pub enum PeerResponse<N: NetworkPrimitives = EthNetworkPrimitives> {
/// The receiver channel for the response to a receipts request. /// The receiver channel for the response to a receipts request.
response: oneshot::Receiver<RequestResult<Receipts69<N::Receipt>>>, response: oneshot::Receiver<RequestResult<Receipts69<N::Receipt>>>,
}, },
/// Represents a response to a request for receipts using eth/70.
Receipts70 {
/// The receiver channel for the response to a receipts request.
response: oneshot::Receiver<RequestResult<Receipts70<N::Receipt>>>,
},
} }
// === impl PeerResponse === // === impl PeerResponse ===
@@ -151,6 +156,10 @@ impl<N: NetworkPrimitives> PeerResponse<N> {
Self::Receipts69 { response } => { Self::Receipts69 { response } => {
poll_request!(response, Receipts69, cx) poll_request!(response, Receipts69, cx)
} }
Self::Receipts70 { response } => match ready!(response.poll_unpin(cx)) {
Ok(res) => PeerResponseResult::Receipts70(res),
Err(err) => PeerResponseResult::Receipts70(Err(err.into())),
},
}; };
Poll::Ready(res) Poll::Ready(res)
} }
@@ -171,6 +180,8 @@ pub enum PeerResponseResult<N: NetworkPrimitives = EthNetworkPrimitives> {
Receipts(RequestResult<Vec<Vec<ReceiptWithBloom<N::Receipt>>>>), Receipts(RequestResult<Vec<Vec<ReceiptWithBloom<N::Receipt>>>>),
/// Represents a result containing receipts or an error for eth/69. /// Represents a result containing receipts or an error for eth/69.
Receipts69(RequestResult<Vec<Vec<N::Receipt>>>), Receipts69(RequestResult<Vec<Vec<N::Receipt>>>),
/// Represents a result containing receipts or an error for eth/70.
Receipts70(RequestResult<Receipts70<N::Receipt>>),
} }
// === impl PeerResponseResult === // === impl PeerResponseResult ===
@@ -208,6 +219,13 @@ impl<N: NetworkPrimitives> PeerResponseResult<N> {
Self::Receipts69(resp) => { Self::Receipts69(resp) => {
to_message!(resp, Receipts69, id) to_message!(resp, Receipts69, id)
} }
Self::Receipts70(resp) => match resp {
Ok(res) => {
let request = RequestPair { request_id: id, message: res };
Ok(EthMessage::Receipts70(request))
}
Err(err) => Err(err),
},
} }
} }
@@ -220,6 +238,7 @@ impl<N: NetworkPrimitives> PeerResponseResult<N> {
Self::NodeData(res) => res.as_ref().err(), Self::NodeData(res) => res.as_ref().err(),
Self::Receipts(res) => res.as_ref().err(), Self::Receipts(res) => res.as_ref().err(),
Self::Receipts69(res) => res.as_ref().err(), Self::Receipts69(res) => res.as_ref().err(),
Self::Receipts70(res) => res.as_ref().err(),
} }
} }

View File

@@ -25,10 +25,10 @@ use futures::{stream::Fuse, SinkExt, StreamExt};
use metrics::Gauge; use metrics::Gauge;
use reth_eth_wire::{ use reth_eth_wire::{
errors::{EthHandshakeError, EthStreamError}, errors::{EthHandshakeError, EthStreamError},
message::{EthBroadcastMessage, MessageError, RequestPair}, message::{EthBroadcastMessage, MessageError},
Capabilities, DisconnectP2P, DisconnectReason, EthMessage, NetworkPrimitives, NewBlockPayload, Capabilities, DisconnectP2P, DisconnectReason, EthMessage, NetworkPrimitives, NewBlockPayload,
}; };
use reth_eth_wire_types::RawCapabilityMessage; use reth_eth_wire_types::{message::RequestPair, RawCapabilityMessage};
use reth_metrics::common::mpsc::MeteredPollSender; use reth_metrics::common::mpsc::MeteredPollSender;
use reth_network_api::PeerRequest; use reth_network_api::PeerRequest;
use reth_network_p2p::error::RequestError; use reth_network_p2p::error::RequestError;
@@ -270,12 +270,18 @@ impl<N: NetworkPrimitives> ActiveSession<N> {
on_request!(req, Receipts, GetReceipts) on_request!(req, Receipts, GetReceipts)
} }
} }
EthMessage::GetReceipts70(req) => {
on_request!(req, Receipts70, GetReceipts70)
}
EthMessage::Receipts(resp) => { EthMessage::Receipts(resp) => {
on_response!(resp, GetReceipts) on_response!(resp, GetReceipts)
} }
EthMessage::Receipts69(resp) => { EthMessage::Receipts69(resp) => {
on_response!(resp, GetReceipts69) on_response!(resp, GetReceipts69)
} }
EthMessage::Receipts70(resp) => {
on_response!(resp, GetReceipts70)
}
EthMessage::BlockRangeUpdate(msg) => { EthMessage::BlockRangeUpdate(msg) => {
// Validate that earliest <= latest according to the spec // Validate that earliest <= latest according to the spec
if msg.earliest > msg.latest { if msg.earliest > msg.latest {
@@ -311,9 +317,9 @@ impl<N: NetworkPrimitives> ActiveSession<N> {
/// Handle an internal peer request that will be sent to the remote. /// Handle an internal peer request that will be sent to the remote.
fn on_internal_peer_request(&mut self, request: PeerRequest<N>, deadline: Instant) { fn on_internal_peer_request(&mut self, request: PeerRequest<N>, deadline: Instant) {
let request_id = self.next_id(); let request_id = self.next_id();
trace!(?request, peer_id=?self.remote_peer_id, ?request_id, "sending request to peer"); trace!(?request, peer_id=?self.remote_peer_id, ?request_id, "sending request to peer");
let msg = request.create_request_message(request_id); let msg = request.create_request_message(request_id).map_versioned(self.conn.version());
self.queued_outgoing.push_back(msg.into()); self.queued_outgoing.push_back(msg.into());
let req = InflightRequest { let req = InflightRequest {
request: RequestState::Waiting(request), request: RequestState::Waiting(request),

View File

@@ -1924,7 +1924,9 @@ impl PooledTransactionsHashesBuilder {
fn new(version: EthVersion) -> Self { fn new(version: EthVersion) -> Self {
match version { match version {
EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()), EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()),
EthVersion::Eth68 | EthVersion::Eth69 => Self::Eth68(Default::default()), EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 => {
Self::Eth68(Default::default())
}
} }
} }

View File

@@ -413,3 +413,6 @@ additional "satellite" protocols (e.g. `snap`) using negotiated `SharedCapabilit
- Starting with ETH69: - Starting with ETH69:
- `BlockRangeUpdate (0x11)` announces the historical block range served. - `BlockRangeUpdate (0x11)` announces the historical block range served.
- Receipts omit bloom: encoded as `Receipts69` instead of `Receipts`. - Receipts omit bloom: encoded as `Receipts69` instead of `Receipts`.
- Starting with ETH70 (EIP-7975):
- Status reuses the ETH69 format (no additional block range fields).
- Receipts continue to omit bloom; `GetReceipts`/`Receipts` add the eth/70 variants to support partial receipt ranges (`firstBlockReceiptIndex` and `lastBlockIncomplete`).

View File

@@ -82,6 +82,7 @@ async fn main() -> eyre::Result<()> {
IncomingEthRequest::GetNodeData { .. } => {} IncomingEthRequest::GetNodeData { .. } => {}
IncomingEthRequest::GetReceipts { .. } => {} IncomingEthRequest::GetReceipts { .. } => {}
IncomingEthRequest::GetReceipts69 { .. } => {} IncomingEthRequest::GetReceipts69 { .. } => {}
IncomingEthRequest::GetReceipts70 { .. } => {}
} }
} }
transaction_message = transactions_rx.recv() => { transaction_message = transactions_rx.recv() => {