From 7b43c5ee9097fa603af4c3e053206e1a8a685010 Mon Sep 17 00:00:00 2001 From: Shane K Moore <41407272+shane-moore@users.noreply.github.com> Date: Mon, 7 Apr 2025 01:21:30 -0700 Subject: [PATCH] chore: add status enum for handshake to support status69 decoding (#15543) Co-authored-by: Matthias Seitz --- crates/net/eth-wire-types/src/lib.rs | 2 +- crates/net/eth-wire-types/src/message.rs | 16 +++-- crates/net/eth-wire-types/src/status.rs | 81 +++++++++++++++++++++++- crates/net/eth-wire/src/handshake.rs | 26 ++++---- 4 files changed, 108 insertions(+), 17 deletions(-) diff --git a/crates/net/eth-wire-types/src/lib.rs b/crates/net/eth-wire-types/src/lib.rs index 2fbe81ecd0..cabbc10828 100644 --- a/crates/net/eth-wire-types/src/lib.rs +++ b/crates/net/eth-wire-types/src/lib.rs @@ -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}; diff --git a/crates/net/eth-wire-types/src/message.rs b/crates/net/eth-wire-types/src/message.rs index 2208f14384..4af1df157f 100644 --- a/crates/net/eth-wire-types/src/message.rs +++ b/crates/net/eth-wire-types/src/message.rs @@ -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 ProtocolMessage { pub fn decode_message(version: EthVersion, buf: &mut &[u8]) -> Result { 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 From> for ProtocolBroadcastMes #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum EthMessage { /// 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. diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 2c759695ba..89df2f2007 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -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 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 + /// `` + 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}; diff --git a/crates/net/eth-wire/src/handshake.rs b/crates/net/eth-wire/src/handshake.rs index 23997e716b..a7c9384c1b 100644 --- a/crates/net/eth-wire/src/handshake.rs +++ b/crates/net/eth-wire/src/handshake.rs @@ -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::::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