From 4b0fa8a3302a8ad38f8837dfa2488d4c3b6e53bc Mon Sep 17 00:00:00 2001 From: Karl Yu <43113774+0xKarl98@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:22:26 +0800 Subject: [PATCH] feat: implement variants for BAL devp2p variants (#22024) Co-authored-by: Matthias Seitz Co-authored-by: Amp --- .../eth-wire-types/src/block_access_lists.rs | 27 +++++ crates/net/eth-wire-types/src/broadcast.rs | 5 +- crates/net/eth-wire-types/src/capability.rs | 27 ++++- crates/net/eth-wire-types/src/lib.rs | 3 + crates/net/eth-wire-types/src/message.rs | 113 ++++++++++++++++-- crates/net/eth-wire-types/src/version.rs | 23 +++- crates/net/eth-wire/src/eth_snap_stream.rs | 39 +++++- crates/net/eth-wire/src/protocol.rs | 2 + crates/net/network-api/src/events.rs | 49 +++++++- crates/net/network/src/eth_requests.rs | 34 +++++- crates/net/network/src/manager.rs | 7 ++ crates/net/network/src/message.rs | 21 +++- crates/net/network/src/session/active.rs | 48 +++++++- crates/net/network/src/transactions/mod.rs | 2 +- examples/network-proxy/src/main.rs | 1 + 15 files changed, 373 insertions(+), 28 deletions(-) create mode 100644 crates/net/eth-wire-types/src/block_access_lists.rs diff --git a/crates/net/eth-wire-types/src/block_access_lists.rs b/crates/net/eth-wire-types/src/block_access_lists.rs new file mode 100644 index 0000000000..13e7559c4e --- /dev/null +++ b/crates/net/eth-wire-types/src/block_access_lists.rs @@ -0,0 +1,27 @@ +//! Implements the `GetBlockAccessLists` and `BlockAccessLists` message types. + +use alloc::vec::Vec; +use alloy_primitives::{Bytes, B256}; +use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; +use reth_codecs_derive::add_arbitrary_tests; + +/// A request for block access lists from the given block hashes. +#[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +#[add_arbitrary_tests(rlp)] +pub struct GetBlockAccessLists( + /// The block hashes to request block access lists for. + pub Vec, +); + +/// Response for [`GetBlockAccessLists`] containing one BAL per requested block hash. +#[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +#[add_arbitrary_tests(rlp)] +pub struct BlockAccessLists( + /// The requested block access lists as opaque bytes. Unavailable entries are represented by + /// empty byte slices. + pub Vec, +); diff --git a/crates/net/eth-wire-types/src/broadcast.rs b/crates/net/eth-wire-types/src/broadcast.rs index a4d0c74d28..eb87ba2ac0 100644 --- a/crates/net/eth-wire-types/src/broadcast.rs +++ b/crates/net/eth-wire-types/src/broadcast.rs @@ -169,7 +169,10 @@ impl NewPooledTransactionHashes { matches!(version, EthVersion::Eth67 | EthVersion::Eth66) } Self::Eth68(_) => { - matches!(version, EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70) + matches!( + version, + EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 + ) } } } diff --git a/crates/net/eth-wire-types/src/capability.rs b/crates/net/eth-wire-types/src/capability.rs index d35e4c17ee..1984e3f781 100644 --- a/crates/net/eth-wire-types/src/capability.rs +++ b/crates/net/eth-wire-types/src/capability.rs @@ -110,6 +110,11 @@ impl Capability { Self::eth(EthVersion::Eth70) } + /// Returns the [`EthVersion::Eth71`] capability. + pub const fn eth_71() -> Self { + Self::eth(EthVersion::Eth71) + } + /// Whether this is eth v66 protocol. #[inline] pub fn is_eth_v66(&self) -> bool { @@ -140,6 +145,12 @@ impl Capability { self.name == "eth" && self.version == 70 } + /// Whether this is eth v71. + #[inline] + pub fn is_eth_v71(&self) -> bool { + self.name == "eth" && self.version == 71 + } + /// Whether this is any eth version. #[inline] pub fn is_eth(&self) -> bool { @@ -147,7 +158,8 @@ impl Capability { self.is_eth_v67() || self.is_eth_v68() || self.is_eth_v69() || - self.is_eth_v70() + self.is_eth_v70() || + self.is_eth_v71() } } @@ -167,7 +179,7 @@ impl From for Capability { #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for Capability { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let version = u.int_in_range(66..=70)?; // Valid eth protocol versions are 66-70 + let version = u.int_in_range(66..=71)?; // Valid eth protocol versions are 66-71 // Only generate valid eth protocol name for now since it's the only supported protocol Ok(Self::new_static("eth", version)) } @@ -183,6 +195,7 @@ pub struct Capabilities { eth_68: bool, eth_69: bool, eth_70: bool, + eth_71: bool, } impl Capabilities { @@ -194,6 +207,7 @@ impl Capabilities { 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), + eth_71: value.iter().any(Capability::is_eth_v71), inner: value, } } @@ -212,7 +226,7 @@ impl Capabilities { /// Whether the peer supports `eth` sub-protocol. #[inline] pub const fn supports_eth(&self) -> bool { - self.eth_70 || self.eth_69 || self.eth_68 || self.eth_67 || self.eth_66 + self.eth_71 || self.eth_70 || self.eth_69 || self.eth_68 || self.eth_67 || self.eth_66 } /// Whether this peer supports eth v66 protocol. @@ -244,6 +258,12 @@ impl Capabilities { pub const fn supports_eth_v70(&self) -> bool { self.eth_70 } + + /// Whether this peer supports eth v71 protocol. + #[inline] + pub const fn supports_eth_v71(&self) -> bool { + self.eth_71 + } } impl From> for Capabilities { @@ -268,6 +288,7 @@ impl Decodable for Capabilities { 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), + eth_71: inner.iter().any(Capability::is_eth_v71), inner, }) } diff --git a/crates/net/eth-wire-types/src/lib.rs b/crates/net/eth-wire-types/src/lib.rs index b7d2722784..de6e49de53 100644 --- a/crates/net/eth-wire-types/src/lib.rs +++ b/crates/net/eth-wire-types/src/lib.rs @@ -38,6 +38,9 @@ pub use state::*; pub mod receipts; pub use receipts::*; +pub mod block_access_lists; +pub use block_access_lists::*; + pub mod disconnect_reason; pub use disconnect_reason::*; diff --git a/crates/net/eth-wire-types/src/message.rs b/crates/net/eth-wire-types/src/message.rs index 5ef5947548..d1a5843b4f 100644 --- a/crates/net/eth-wire-types/src/message.rs +++ b/crates/net/eth-wire-types/src/message.rs @@ -1,4 +1,4 @@ -//! Implements Ethereum wire protocol for versions 66 through 70. +//! Implements Ethereum wire protocol for versions 66 through 71. //! Defines structs/enums for messages, request-response pairs, and broadcasts. //! Handles compatibility with [`EthVersion`]. //! @@ -7,10 +7,10 @@ //! Reference: [Ethereum Wire Protocol](https://github.com/ethereum/devp2p/blob/master/caps/eth.md). use super::{ - broadcast::NewBlockHashes, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders, - GetNodeData, GetPooledTransactions, GetReceipts, GetReceipts70, NewPooledTransactionHashes66, - NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, StatusEth69, - Transactions, + broadcast::NewBlockHashes, BlockAccessLists, BlockBodies, BlockHeaders, GetBlockAccessLists, + GetBlockBodies, GetBlockHeaders, GetNodeData, GetPooledTransactions, GetReceipts, + GetReceipts70, NewPooledTransactionHashes66, NewPooledTransactionHashes68, NodeData, + PooledTransactions, Receipts, Status, StatusEth69, Transactions, }; use crate::{ status::StatusMessage, BlockRangeUpdate, EthNetworkPrimitives, EthVersion, NetworkPrimitives, @@ -168,6 +168,18 @@ impl ProtocolMessage { } EthMessage::BlockRangeUpdate(BlockRangeUpdate::decode(buf)?) } + EthMessageID::GetBlockAccessLists => { + if version < EthVersion::Eth71 { + return Err(MessageError::Invalid(version, EthMessageID::GetBlockAccessLists)) + } + EthMessage::GetBlockAccessLists(RequestPair::decode(buf)?) + } + EthMessageID::BlockAccessLists => { + if version < EthVersion::Eth71 { + return Err(MessageError::Invalid(version, EthMessageID::BlockAccessLists)) + } + EthMessage::BlockAccessLists(RequestPair::decode(buf)?) + } EthMessageID::Other(_) => { let raw_payload = Bytes::copy_from_slice(buf); buf.advance(raw_payload.len()); @@ -250,6 +262,8 @@ impl From> for ProtocolBroadcastMes /// /// The `eth/70` (EIP-7975) keeps the eth/69 status format and introduces partial receipts. /// requests/responses. +/// +/// The `eth/71` draft extends eth/70 with block access list request/response messages. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum EthMessage { @@ -310,6 +324,8 @@ pub enum EthMessage { /// `GetReceipts` in EIP-7975 inlines the request id. The type still wraps /// a [`RequestPair`], but with a custom inline encoding. GetReceipts70(RequestPair), + /// Represents a `GetBlockAccessLists` request-response pair for eth/71. + GetBlockAccessLists(RequestPair), /// Represents a Receipts request-response pair. #[cfg_attr( feature = "serde", @@ -332,6 +348,8 @@ pub enum EthMessage { /// request id. The type still wraps a [`RequestPair`], but with a custom /// inline encoding. Receipts70(RequestPair>), + /// Represents a `BlockAccessLists` request-response pair for eth/71. + BlockAccessLists(RequestPair), /// Represents a `BlockRangeUpdate` message broadcast to the network. #[cfg_attr( feature = "serde", @@ -364,6 +382,8 @@ impl EthMessage { Self::GetReceipts(_) | Self::GetReceipts70(_) => EthMessageID::GetReceipts, Self::Receipts(_) | Self::Receipts69(_) | Self::Receipts70(_) => EthMessageID::Receipts, Self::BlockRangeUpdate(_) => EthMessageID::BlockRangeUpdate, + Self::GetBlockAccessLists(_) => EthMessageID::GetBlockAccessLists, + Self::BlockAccessLists(_) => EthMessageID::BlockAccessLists, Self::Other(msg) => EthMessageID::Other(msg.id as u8), } } @@ -376,6 +396,7 @@ impl EthMessage { Self::GetBlockHeaders(_) | Self::GetReceipts(_) | Self::GetReceipts70(_) | + Self::GetBlockAccessLists(_) | Self::GetPooledTransactions(_) | Self::GetNodeData(_) ) @@ -389,6 +410,7 @@ impl EthMessage { Self::Receipts(_) | Self::Receipts69(_) | Self::Receipts70(_) | + Self::BlockAccessLists(_) | Self::BlockHeaders(_) | Self::BlockBodies(_) | Self::NodeData(_) @@ -443,9 +465,11 @@ impl Encodable for EthMessage { Self::NodeData(data) => data.encode(out), Self::GetReceipts(request) => request.encode(out), Self::GetReceipts70(request) => request.encode(out), + Self::GetBlockAccessLists(request) => request.encode(out), Self::Receipts(receipts) => receipts.encode(out), Self::Receipts69(receipt69) => receipt69.encode(out), Self::Receipts70(receipt70) => receipt70.encode(out), + Self::BlockAccessLists(block_access_lists) => block_access_lists.encode(out), Self::BlockRangeUpdate(block_range_update) => block_range_update.encode(out), Self::Other(unknown) => out.put_slice(&unknown.payload), } @@ -468,9 +492,11 @@ impl Encodable for EthMessage { Self::NodeData(data) => data.length(), Self::GetReceipts(request) => request.length(), Self::GetReceipts70(request) => request.length(), + Self::GetBlockAccessLists(request) => request.length(), Self::Receipts(receipts) => receipts.length(), Self::Receipts69(receipt69) => receipt69.length(), Self::Receipts70(receipt70) => receipt70.length(), + Self::BlockAccessLists(block_access_lists) => block_access_lists.length(), Self::BlockRangeUpdate(block_range_update) => block_range_update.length(), Self::Other(unknown) => unknown.length(), } @@ -559,6 +585,14 @@ pub enum EthMessageID { /// /// Introduced in Eth69 BlockRangeUpdate = 0x11, + /// Requests block access lists. + /// + /// Introduced in Eth71 + GetBlockAccessLists = 0x12, + /// Represents block access lists. + /// + /// Introduced in Eth71 + BlockAccessLists = 0x13, /// Represents unknown message types. Other(u8), } @@ -583,13 +617,17 @@ impl EthMessageID { Self::GetReceipts => 0x0f, Self::Receipts => 0x10, Self::BlockRangeUpdate => 0x11, + Self::GetBlockAccessLists => 0x12, + Self::BlockAccessLists => 0x13, Self::Other(value) => *value, // Return the stored `u8` } } /// Returns the max value for the given version. pub const fn max(version: EthVersion) -> u8 { - if version as u8 >= EthVersion::Eth69 as u8 { + if version.is_eth71() { + Self::BlockAccessLists.to_u8() + } else if version.is_eth69_or_newer() { Self::BlockRangeUpdate.to_u8() } else { Self::Receipts.to_u8() @@ -634,6 +672,8 @@ impl Decodable for EthMessageID { 0x0f => Self::GetReceipts, 0x10 => Self::Receipts, 0x11 => Self::BlockRangeUpdate, + 0x12 => Self::GetBlockAccessLists, + 0x13 => Self::BlockAccessLists, unknown => Self::Other(*unknown), }; buf.advance(1); @@ -662,6 +702,8 @@ impl TryFrom for EthMessageID { 0x0f => Ok(Self::GetReceipts), 0x10 => Ok(Self::Receipts), 0x11 => Ok(Self::BlockRangeUpdate), + 0x12 => Ok(Self::GetBlockAccessLists), + 0x13 => Ok(Self::BlockAccessLists), _ => Err("Invalid message ID"), } } @@ -742,8 +784,9 @@ where mod tests { use super::MessageError; use crate::{ - message::RequestPair, EthMessage, EthMessageID, EthNetworkPrimitives, EthVersion, - GetNodeData, NodeData, ProtocolMessage, RawCapabilityMessage, + message::RequestPair, BlockAccessLists, EthMessage, EthMessageID, EthNetworkPrimitives, + EthVersion, GetBlockAccessLists, GetNodeData, NodeData, ProtocolMessage, + RawCapabilityMessage, }; use alloy_primitives::hex; use alloy_rlp::{Decodable, Encodable, Error}; @@ -784,6 +827,60 @@ mod tests { assert!(matches!(msg, Err(MessageError::Invalid(..)))); } + #[test] + fn test_bal_message_version_gating() { + let get_block_access_lists = + EthMessage::::GetBlockAccessLists(RequestPair { + request_id: 1337, + message: GetBlockAccessLists(vec![]), + }); + let buf = encode(ProtocolMessage { + message_type: EthMessageID::GetBlockAccessLists, + message: get_block_access_lists, + }); + let msg = ProtocolMessage::::decode_message( + EthVersion::Eth70, + &mut &buf[..], + ); + assert!(matches!( + msg, + Err(MessageError::Invalid(EthVersion::Eth70, EthMessageID::GetBlockAccessLists)) + )); + + let block_access_lists = + EthMessage::::BlockAccessLists(RequestPair { + request_id: 1337, + message: BlockAccessLists(vec![]), + }); + let buf = encode(ProtocolMessage { + message_type: EthMessageID::BlockAccessLists, + message: block_access_lists, + }); + let msg = ProtocolMessage::::decode_message( + EthVersion::Eth70, + &mut &buf[..], + ); + assert!(matches!( + msg, + Err(MessageError::Invalid(EthVersion::Eth70, EthMessageID::BlockAccessLists)) + )); + } + + #[test] + fn test_bal_message_eth71_roundtrip() { + let msg = ProtocolMessage::from(EthMessage::::GetBlockAccessLists( + RequestPair { request_id: 42, message: GetBlockAccessLists(vec![]) }, + )); + let encoded = encode(msg.clone()); + let decoded = ProtocolMessage::::decode_message( + EthVersion::Eth71, + &mut &encoded[..], + ) + .unwrap(); + + assert_eq!(decoded, msg); + } + #[test] fn request_pair_encode() { let request_pair = RequestPair { request_id: 1337, message: vec![5u8] }; diff --git a/crates/net/eth-wire-types/src/version.rs b/crates/net/eth-wire-types/src/version.rs index 6553bd2e41..d9921c44b9 100644 --- a/crates/net/eth-wire-types/src/version.rs +++ b/crates/net/eth-wire-types/src/version.rs @@ -29,6 +29,8 @@ pub enum EthVersion { Eth69 = 69, /// The `eth` protocol version 70. Eth70 = 70, + /// The `eth` protocol version 71. + Eth71 = 71, } impl EthVersion { @@ -62,9 +64,19 @@ impl EthVersion { pub const fn is_eth70(&self) -> bool { matches!(self, Self::Eth70) } + + /// Returns true if the version is eth/71 + pub const fn is_eth71(&self) -> bool { + matches!(self, Self::Eth71) + } + + /// Returns true if the version is eth/69 or newer. + pub const fn is_eth69_or_newer(&self) -> bool { + matches!(self, Self::Eth69 | Self::Eth70 | Self::Eth71) + } } -/// RLP encodes `EthVersion` as a single byte (66-69). +/// RLP encodes `EthVersion` as a single byte (66-71). impl Encodable for EthVersion { fn encode(&self, out: &mut dyn BufMut) { (*self as u8).encode(out) @@ -76,7 +88,7 @@ impl Encodable for EthVersion { } /// RLP decodes a single byte into `EthVersion`. -/// Returns error if byte is not a valid version (66-69). +/// Returns error if byte is not a valid version (66-71). impl Decodable for EthVersion { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { let version = u8::decode(buf)?; @@ -104,6 +116,7 @@ impl TryFrom<&str> for EthVersion { "68" => Ok(Self::Eth68), "69" => Ok(Self::Eth69), "70" => Ok(Self::Eth70), + "71" => Ok(Self::Eth71), _ => Err(ParseVersionError(s.to_string())), } } @@ -129,6 +142,7 @@ impl TryFrom for EthVersion { 68 => Ok(Self::Eth68), 69 => Ok(Self::Eth69), 70 => Ok(Self::Eth70), + 71 => Ok(Self::Eth71), _ => Err(ParseVersionError(u.to_string())), } } @@ -159,6 +173,7 @@ impl From for &'static str { EthVersion::Eth68 => "68", EthVersion::Eth69 => "69", EthVersion::Eth70 => "70", + EthVersion::Eth71 => "71", } } } @@ -216,6 +231,7 @@ mod tests { assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap()); assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap()); assert_eq!(EthVersion::Eth70, EthVersion::try_from("70").unwrap()); + assert_eq!(EthVersion::Eth71, EthVersion::try_from("71").unwrap()); } #[test] @@ -225,6 +241,7 @@ mod tests { assert_eq!(EthVersion::Eth68, "68".parse().unwrap()); assert_eq!(EthVersion::Eth69, "69".parse().unwrap()); assert_eq!(EthVersion::Eth70, "70".parse().unwrap()); + assert_eq!(EthVersion::Eth71, "71".parse().unwrap()); } #[test] @@ -235,6 +252,7 @@ mod tests { EthVersion::Eth68, EthVersion::Eth69, EthVersion::Eth70, + EthVersion::Eth71, ]; for version in versions { @@ -253,6 +271,7 @@ mod tests { (68_u8, Ok(EthVersion::Eth68)), (69_u8, Ok(EthVersion::Eth69)), (70_u8, Ok(EthVersion::Eth70)), + (71_u8, Ok(EthVersion::Eth71)), (65_u8, Err(RlpError::Custom("invalid eth version"))), ]; diff --git a/crates/net/eth-wire/src/eth_snap_stream.rs b/crates/net/eth-wire/src/eth_snap_stream.rs index 43b91a7fd5..114837075c 100644 --- a/crates/net/eth-wire/src/eth_snap_stream.rs +++ b/crates/net/eth-wire/src/eth_snap_stream.rs @@ -294,7 +294,8 @@ mod tests { use alloy_primitives::B256; use alloy_rlp::Encodable; use reth_eth_wire_types::{ - message::RequestPair, GetAccountRangeMessage, GetBlockHeaders, HeadersDirection, + message::RequestPair, GetAccountRangeMessage, GetBlockAccessLists, GetBlockHeaders, + HeadersDirection, }; // Helper to create eth message and its bytes @@ -419,4 +420,40 @@ mod tests { let snap_boundary_result = inner.decode_message(snap_boundary_bytes); assert!(snap_boundary_result.is_err()); } + + #[test] + fn test_eth70_message_id_0x12_is_snap() { + let inner = EthSnapStreamInner::::new(EthVersion::Eth70); + let snap_msg = SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage { + request_id: 1, + root_hash: B256::default(), + starting_hash: B256::default(), + limit_hash: B256::default(), + response_bytes: 1000, + }); + + let encoded = inner.encode_snap_message(snap_msg); + assert_eq!(encoded[0], EthMessageID::message_count(EthVersion::Eth70)); + + let decoded = inner.decode_message(BytesMut::from(&encoded[..])).unwrap(); + assert!(matches!(decoded, EthSnapMessage::Snap(_))); + } + + #[test] + fn test_eth71_message_id_0x12_is_eth() { + let inner = EthSnapStreamInner::::new(EthVersion::Eth71); + let eth_msg = EthMessage::::GetBlockAccessLists(RequestPair { + request_id: 1, + message: GetBlockAccessLists(vec![B256::ZERO]), + }); + let protocol_msg = ProtocolMessage::from(eth_msg.clone()); + let mut buf = Vec::new(); + protocol_msg.encode(&mut buf); + + let decoded = inner.decode_message(BytesMut::from(&buf[..])).unwrap(); + let EthSnapMessage::Eth(decoded_eth) = decoded else { + panic!("expected eth message"); + }; + assert_eq!(decoded_eth, eth_msg); + } } diff --git a/crates/net/eth-wire/src/protocol.rs b/crates/net/eth-wire/src/protocol.rs index 16ec62b7cd..488f402aa5 100644 --- a/crates/net/eth-wire/src/protocol.rs +++ b/crates/net/eth-wire/src/protocol.rs @@ -84,5 +84,7 @@ mod tests { assert_eq!(Protocol::eth(EthVersion::Eth67).messages(), 17); assert_eq!(Protocol::eth(EthVersion::Eth68).messages(), 17); assert_eq!(Protocol::eth(EthVersion::Eth69).messages(), 18); + assert_eq!(Protocol::eth(EthVersion::Eth70).messages(), 18); + assert_eq!(Protocol::eth(EthVersion::Eth71).messages(), 20); } } diff --git a/crates/net/network-api/src/events.rs b/crates/net/network-api/src/events.rs index d0cf95a8b7..5ec061f204 100644 --- a/crates/net/network-api/src/events.rs +++ b/crates/net/network-api/src/events.rs @@ -1,10 +1,11 @@ //! API related to listening for network events. use reth_eth_wire_types::{ - message::RequestPair, BlockBodies, BlockHeaders, Capabilities, DisconnectReason, EthMessage, - EthNetworkPrimitives, EthVersion, GetBlockBodies, GetBlockHeaders, GetNodeData, - GetPooledTransactions, GetReceipts, GetReceipts70, NetworkPrimitives, NodeData, - PooledTransactions, Receipts, Receipts69, Receipts70, UnifiedStatus, + message::RequestPair, BlockAccessLists, BlockBodies, BlockHeaders, Capabilities, + DisconnectReason, EthMessage, EthNetworkPrimitives, EthVersion, GetBlockAccessLists, + GetBlockBodies, GetBlockHeaders, GetNodeData, GetPooledTransactions, GetReceipts, + GetReceipts70, NetworkPrimitives, NodeData, PooledTransactions, Receipts, Receipts69, + Receipts70, UnifiedStatus, }; use reth_ethereum_forks::ForkId; use reth_network_p2p::error::{RequestError, RequestResult}; @@ -252,6 +253,15 @@ pub enum PeerRequest { /// The channel to send the response for receipts. response: oneshot::Sender>>, }, + /// Requests block access lists from the peer. + /// + /// The response should be sent through the channel. + GetBlockAccessLists { + /// The request for block access lists. + request: GetBlockAccessLists, + /// The channel to send the response for block access lists. + response: oneshot::Sender>, + }, } // === impl PeerRequest === @@ -272,9 +282,19 @@ impl PeerRequest { Self::GetReceipts { response, .. } => response.send(Err(err)).ok(), Self::GetReceipts69 { response, .. } => response.send(Err(err)).ok(), Self::GetReceipts70 { response, .. } => response.send(Err(err)).ok(), + Self::GetBlockAccessLists { response, .. } => response.send(Err(err)).ok(), }; } + /// Returns true if this request is supported for the negotiated eth protocol version. + #[inline] + pub fn is_supported_by_eth_version(&self, version: EthVersion) -> bool { + match self { + Self::GetBlockAccessLists { .. } => version >= EthVersion::Eth71, + _ => true, + } + } + /// Returns the [`EthMessage`] for this type pub fn create_request_message(&self, request_id: u64) -> EthMessage { match self { @@ -299,6 +319,12 @@ impl PeerRequest { Self::GetReceipts70 { request, .. } => { EthMessage::GetReceipts70(RequestPair { request_id, message: request.clone() }) } + Self::GetBlockAccessLists { request, .. } => { + EthMessage::GetBlockAccessLists(RequestPair { + request_id, + message: request.clone(), + }) + } } } @@ -349,3 +375,18 @@ impl fmt::Debug for PeerRequestSender { f.debug_struct("PeerRequestSender").field("peer_id", &self.peer_id).finish_non_exhaustive() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_block_access_lists_version_support() { + let (tx, _rx) = oneshot::channel(); + let req: PeerRequest = + PeerRequest::GetBlockAccessLists { request: GetBlockAccessLists(vec![]), response: tx }; + + assert!(!req.is_supported_by_eth_version(EthVersion::Eth70)); + assert!(req.is_supported_by_eth_version(EthVersion::Eth71)); + } +} diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index c110c5b11b..1b28af56a9 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -6,12 +6,13 @@ use crate::{ }; use alloy_consensus::{BlockHeader, ReceiptWithBloom}; use alloy_eips::BlockHashOrNumber; +use alloy_primitives::Bytes; use alloy_rlp::Encodable; use futures::StreamExt; use reth_eth_wire::{ - BlockBodies, BlockHeaders, EthNetworkPrimitives, GetBlockBodies, GetBlockHeaders, GetNodeData, - GetReceipts, GetReceipts70, HeadersDirection, NetworkPrimitives, NodeData, Receipts, - Receipts69, Receipts70, + BlockAccessLists, BlockBodies, BlockHeaders, EthNetworkPrimitives, GetBlockAccessLists, + GetBlockBodies, GetBlockHeaders, GetNodeData, GetReceipts, GetReceipts70, HeadersDirection, + NetworkPrimitives, NodeData, Receipts, Receipts69, Receipts70, }; use reth_network_api::test_utils::PeersHandle; use reth_network_p2p::error::RequestResult; @@ -281,6 +282,19 @@ where let _ = response.send(Ok(Receipts70 { last_block_incomplete, receipts })); } + /// Handles [`GetBlockAccessLists`] queries. + /// + /// For now this returns one empty BAL per requested hash. + fn on_block_access_lists_request( + &self, + _peer_id: PeerId, + request: GetBlockAccessLists, + response: oneshot::Sender>, + ) { + let access_lists = request.0.into_iter().map(|_| Bytes::new()).collect(); + let _ = response.send(Ok(BlockAccessLists(access_lists))); + } + #[inline] fn get_receipts_response(&self, request: GetReceipts, transform_fn: F) -> Vec> where @@ -352,6 +366,9 @@ where IncomingEthRequest::GetReceipts70 { peer_id, request, response } => { this.on_receipts70_request(peer_id, request, response) } + IncomingEthRequest::GetBlockAccessLists { peer_id, request, response } => { + this.on_block_access_lists_request(peer_id, request, response) + } } }, ); @@ -437,4 +454,15 @@ pub enum IncomingEthRequest { /// The channel sender for the response containing Receipts70. response: oneshot::Sender>>, }, + /// Request Block Access Lists from the peer. + /// + /// The response should be sent through the channel. + GetBlockAccessLists { + /// The ID of the peer to request block access lists from. + peer_id: PeerId, + /// The requested block hashes. + request: GetBlockAccessLists, + /// The channel sender for the response containing block access lists. + response: oneshot::Sender>, + }, } diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 975d20be40..0710fdf2b5 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -551,6 +551,13 @@ impl NetworkManager { response, }) } + PeerRequest::GetBlockAccessLists { request, response } => { + self.delegate_eth_request(IncomingEthRequest::GetBlockAccessLists { + peer_id, + request, + response, + }) + } PeerRequest::GetPooledTransactions { request, response } => { self.notify_tx_manager(NetworkTransactionEvent::GetPooledTransactions { peer_id, diff --git a/crates/net/network/src/message.rs b/crates/net/network/src/message.rs index 58df7006e1..9aaaec2812 100644 --- a/crates/net/network/src/message.rs +++ b/crates/net/network/src/message.rs @@ -3,7 +3,7 @@ //! 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)`, -use crate::types::{Receipts69, Receipts70}; +use crate::types::{BlockAccessLists, Receipts69, Receipts70}; use alloy_consensus::{BlockHeader, ReceiptWithBloom}; use alloy_primitives::{Bytes, B256}; use futures::FutureExt; @@ -121,6 +121,11 @@ pub enum PeerResponse { /// The receiver channel for the response to a receipts request. response: oneshot::Receiver>>, }, + /// Represents a response to a request for block access lists. + BlockAccessLists { + /// The receiver channel for the response to a block access lists request. + response: oneshot::Receiver>, + }, } // === impl PeerResponse === @@ -160,6 +165,10 @@ impl PeerResponse { Ok(res) => PeerResponseResult::Receipts70(res), Err(err) => PeerResponseResult::Receipts70(Err(err.into())), }, + Self::BlockAccessLists { response } => match ready!(response.poll_unpin(cx)) { + Ok(res) => PeerResponseResult::BlockAccessLists(res), + Err(err) => PeerResponseResult::BlockAccessLists(Err(err.into())), + }, }; Poll::Ready(res) } @@ -182,6 +191,8 @@ pub enum PeerResponseResult { Receipts69(RequestResult>>), /// Represents a result containing receipts or an error for eth/70. Receipts70(RequestResult>), + /// Represents a result containing block access lists or an error. + BlockAccessLists(RequestResult), } // === impl PeerResponseResult === @@ -226,6 +237,13 @@ impl PeerResponseResult { } Err(err) => Err(err), }, + Self::BlockAccessLists(resp) => match resp { + Ok(res) => { + let request = RequestPair { request_id: id, message: res }; + Ok(EthMessage::BlockAccessLists(request)) + } + Err(err) => Err(err), + }, } } @@ -239,6 +257,7 @@ impl PeerResponseResult { Self::Receipts(res) => res.as_ref().err(), Self::Receipts69(res) => res.as_ref().err(), Self::Receipts70(res) => res.as_ref().err(), + Self::BlockAccessLists(res) => res.as_ref().err(), } } diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 0d405b903e..b437be96a1 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -282,6 +282,12 @@ impl ActiveSession { EthMessage::Receipts70(resp) => { on_response!(resp, GetReceipts70) } + EthMessage::GetBlockAccessLists(req) => { + on_request!(req, BlockAccessLists, GetBlockAccessLists) + } + EthMessage::BlockAccessLists(resp) => { + on_response!(resp, GetBlockAccessLists) + } EthMessage::BlockRangeUpdate(msg) => { // Validate that earliest <= latest according to the spec if msg.earliest > msg.latest { @@ -316,9 +322,22 @@ impl ActiveSession { /// Handle an internal peer request that will be sent to the remote. fn on_internal_peer_request(&mut self, request: PeerRequest, deadline: Instant) { + let version = self.conn.version(); + if !Self::is_request_supported_for_version(&request, version) { + debug!( + target: "net", + ?request, + peer_id=?self.remote_peer_id, + ?version, + "Request not supported for negotiated eth version", + ); + request.send_err_response(RequestError::UnsupportedCapability); + return; + } + let request_id = self.next_id(); trace!(?request, peer_id=?self.remote_peer_id, ?request_id, "sending request to peer"); - let msg = request.create_request_message(request_id).map_versioned(self.conn.version()); + let msg = request.create_request_message(request_id).map_versioned(version); self.queued_outgoing.push_back(msg.into()); let req = InflightRequest { @@ -329,6 +348,11 @@ impl ActiveSession { self.inflight_requests.insert(request_id, req); } + #[inline] + fn is_request_supported_for_version(request: &PeerRequest, version: EthVersion) -> bool { + request.is_supported_by_eth_version(version) + } + /// Handle a message received from the internal network fn on_internal_peer_message(&mut self, msg: PeerMessage) { match msg { @@ -938,9 +962,9 @@ mod tests { use reth_chainspec::MAINNET; use reth_ecies::stream::ECIESStream; use reth_eth_wire::{ - handshake::EthHandshake, EthNetworkPrimitives, EthStream, GetBlockBodies, - HelloMessageWithProtocols, P2PStream, StatusBuilder, UnauthedEthStream, UnauthedP2PStream, - UnifiedStatus, + handshake::EthHandshake, EthNetworkPrimitives, EthStream, GetBlockAccessLists, + GetBlockBodies, HelloMessageWithProtocols, P2PStream, StatusBuilder, UnauthedEthStream, + UnauthedP2PStream, UnifiedStatus, }; use reth_ethereum_forks::EthereumHardfork; use reth_network_peers::pk2id; @@ -1240,6 +1264,22 @@ mod tests { } } + #[test] + fn test_reject_bal_request_for_eth70() { + let (tx, _rx) = oneshot::channel(); + let request: PeerRequest = + PeerRequest::GetBlockAccessLists { request: GetBlockAccessLists(vec![]), response: tx }; + + assert!(!ActiveSession::::is_request_supported_for_version( + &request, + EthVersion::Eth70 + )); + assert!(ActiveSession::::is_request_supported_for_version( + &request, + EthVersion::Eth71 + )); + } + #[tokio::test(flavor = "multi_thread")] async fn test_keep_alive() { let mut builder = SessionBuilder::default(); diff --git a/crates/net/network/src/transactions/mod.rs b/crates/net/network/src/transactions/mod.rs index 466da25eaf..91ee7c17d5 100644 --- a/crates/net/network/src/transactions/mod.rs +++ b/crates/net/network/src/transactions/mod.rs @@ -1949,7 +1949,7 @@ impl PooledTransactionsHashesBuilder { fn new(version: EthVersion) -> Self { match version { EthVersion::Eth66 | EthVersion::Eth67 => Self::Eth66(Default::default()), - EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 => { + EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70 | EthVersion::Eth71 => { Self::Eth68(Default::default()) } } diff --git a/examples/network-proxy/src/main.rs b/examples/network-proxy/src/main.rs index 50ef9e4e72..c7ba2236d3 100644 --- a/examples/network-proxy/src/main.rs +++ b/examples/network-proxy/src/main.rs @@ -83,6 +83,7 @@ async fn main() -> eyre::Result<()> { IncomingEthRequest::GetReceipts { .. } => {} IncomingEthRequest::GetReceipts69 { .. } => {} IncomingEthRequest::GetReceipts70 { .. } => {} + IncomingEthRequest::GetBlockAccessLists { .. } => {} } } transaction_message = transactions_rx.recv() => {