chore: add status enum for handshake to support status69 decoding (#15543)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Shane K Moore
2025-04-07 01:21:30 -07:00
committed by GitHub
parent 1e1f0f8e6b
commit 7b43c5ee90
4 changed files with 108 additions and 17 deletions

View File

@@ -12,7 +12,7 @@
extern crate alloc;
mod status;
pub use status::{Status, StatusBuilder, StatusEth69};
pub use status::{Status, StatusBuilder, StatusEth69, StatusMessage};
pub mod version;
pub use version::{EthVersion, ProtocolVersion};

View File

@@ -9,10 +9,12 @@
use super::{
broadcast::NewBlockHashes, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders,
GetNodeData, GetPooledTransactions, GetReceipts, NewBlock, NewPooledTransactionHashes66,
NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, Transactions,
NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, StatusEth69,
Transactions,
};
use crate::{
EthNetworkPrimitives, EthVersion, NetworkPrimitives, RawCapabilityMessage, SharedTransactions,
status::StatusMessage, EthNetworkPrimitives, EthVersion, NetworkPrimitives,
RawCapabilityMessage, SharedTransactions,
};
use alloc::{boxed::Box, sync::Arc};
use alloy_primitives::{
@@ -56,8 +58,14 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
pub fn decode_message(version: EthVersion, buf: &mut &[u8]) -> Result<Self, MessageError> {
let message_type = EthMessageID::decode(buf)?;
// For EIP-7642 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7642.md):
// pre-merge (legacy) status messages include total difficulty, whereas eth/69 omits it.
let message = match message_type {
EthMessageID::Status => EthMessage::Status(Status::decode(buf)?),
EthMessageID::Status => EthMessage::Status(if version < EthVersion::Eth69 {
StatusMessage::Legacy(Status::decode(buf)?)
} else {
StatusMessage::Eth69(StatusEth69::decode(buf)?)
}),
EthMessageID::NewBlockHashes => {
if version.is_eth69() {
return Err(MessageError::Invalid(version, EthMessageID::NewBlockHashes));
@@ -186,7 +194,7 @@ impl<N: NetworkPrimitives> From<EthBroadcastMessage<N>> for ProtocolBroadcastMes
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
/// Represents a Status message required for the protocol handshake.
Status(Status),
Status(StatusMessage),
/// Represents a `NewBlockHashes` message broadcast to the network.
NewBlockHashes(NewBlockHashes),
/// Represents a `NewBlock` message broadcast to the network.

View File

@@ -2,7 +2,7 @@ use crate::EthVersion;
use alloy_chains::{Chain, NamedChain};
use alloy_hardforks::{EthereumHardfork, ForkId, Head};
use alloy_primitives::{hex, B256, U256};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use alloy_rlp::{BufMut, Encodable, RlpDecodable, RlpEncodable};
use core::fmt::{Debug, Display};
use reth_chainspec::{EthChainSpec, Hardforks, MAINNET};
use reth_codecs_derive::add_arbitrary_tests;
@@ -306,6 +306,85 @@ impl From<Status> for StatusEth69 {
}
}
/// `StatusMessage` can store either the Legacy version (with TD) or the
/// eth/69 version (omits TD).
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StatusMessage {
/// The legacy status (`eth/66` through `eth/68`) with `total_difficulty`.
Legacy(Status),
/// The new `eth/69` status with no `total_difficulty`.
Eth69(StatusEth69),
}
impl StatusMessage {
/// Returns the genesis hash from the status message.
pub const fn genesis(&self) -> B256 {
match self {
Self::Legacy(legacy_status) => legacy_status.genesis,
Self::Eth69(status_69) => status_69.genesis,
}
}
/// Returns the protocol version.
pub const fn version(&self) -> EthVersion {
match self {
Self::Legacy(legacy_status) => legacy_status.version,
Self::Eth69(status_69) => status_69.version,
}
}
/// Returns the chain identifier.
pub const fn chain(&self) -> &Chain {
match self {
Self::Legacy(legacy_status) => &legacy_status.chain,
Self::Eth69(status_69) => &status_69.chain,
}
}
/// Returns the fork identifier.
pub const fn forkid(&self) -> ForkId {
match self {
Self::Legacy(legacy_status) => legacy_status.forkid,
Self::Eth69(status_69) => status_69.forkid,
}
}
/// Converts to legacy Status since full support for EIP-7642
/// is not fully implemented
/// `<https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7642.md>`
pub fn to_legacy(self) -> Status {
match self {
Self::Legacy(legacy_status) => legacy_status,
Self::Eth69(status_69) => Status {
version: status_69.version,
chain: status_69.chain,
// total_difficulty is omitted in Eth69.
total_difficulty: U256::default(),
blockhash: status_69.blockhash,
genesis: status_69.genesis,
forkid: status_69.forkid,
},
}
}
}
impl Encodable for StatusMessage {
fn encode(&self, out: &mut dyn BufMut) {
match self {
Self::Legacy(s) => s.encode(out),
Self::Eth69(s) => s.encode(out),
}
}
fn length(&self) -> usize {
match self {
Self::Legacy(s) => s.length(),
Self::Eth69(s) => s.length(),
}
}
}
#[cfg(test)]
mod tests {
use crate::{EthVersion, Status, StatusEth69};

View File

@@ -6,7 +6,7 @@ use crate::{
use bytes::{Bytes, BytesMut};
use futures::{Sink, SinkExt, Stream};
use reth_eth_wire_types::{
DisconnectReason, EthMessage, EthNetworkPrimitives, ProtocolMessage, Status,
DisconnectReason, EthMessage, EthNetworkPrimitives, ProtocolMessage, Status, StatusMessage,
};
use reth_ethereum_forks::ForkFilter;
use reth_primitives_traits::GotExpected;
@@ -90,7 +90,7 @@ where
alloy_rlp::encode(ProtocolMessage::<EthNetworkPrimitives>::from(EthMessage::<
EthNetworkPrimitives,
>::Status(
status
StatusMessage::Legacy(status),
)))
.into();
unauth.send(status_msg).await.map_err(EthStreamError::from)?;
@@ -135,39 +135,43 @@ where
// Validate peer response
match msg.message {
EthMessage::Status(their_status) => {
EthMessage::Status(their_status_message) => {
trace!("Validating incoming ETH status from peer");
if status.genesis != their_status.genesis {
if status.genesis != their_status_message.genesis() {
unauth
.disconnect(DisconnectReason::ProtocolBreach)
.await
.map_err(EthStreamError::from)?;
return Err(EthHandshakeError::MismatchedGenesis(
GotExpected { expected: status.genesis, got: their_status.genesis }.into(),
GotExpected {
expected: status.genesis,
got: their_status_message.genesis(),
}
.into(),
)
.into());
}
if status.version != their_status.version {
if status.version != their_status_message.version() {
unauth
.disconnect(DisconnectReason::ProtocolBreach)
.await
.map_err(EthStreamError::from)?;
return Err(EthHandshakeError::MismatchedProtocolVersion(GotExpected {
got: their_status.version,
got: their_status_message.version(),
expected: status.version,
})
.into());
}
if status.chain != their_status.chain {
if status.chain != *their_status_message.chain() {
unauth
.disconnect(DisconnectReason::ProtocolBreach)
.await
.map_err(EthStreamError::from)?;
return Err(EthHandshakeError::MismatchedChain(GotExpected {
got: their_status.chain,
got: *their_status_message.chain(),
expected: status.chain,
})
.into());
@@ -188,7 +192,7 @@ where
// Fork validation
if let Err(err) = fork_filter
.validate(their_status.forkid)
.validate(their_status_message.forkid())
.map_err(EthHandshakeError::InvalidFork)
{
unauth
@@ -198,7 +202,7 @@ where
return Err(err.into());
}
Ok(their_status)
Ok(their_status_message.to_legacy())
}
_ => {
unauth