mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-09 07:17:56 -05:00
feat: add support for eth/70 eip-7975 (#20255)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 peer’s network.
|
/// The chain ID identifying the peer’s 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");
|
||||||
|
|||||||
@@ -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"))),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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`).
|
||||||
|
|||||||
@@ -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() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user