diff --git a/crates/common/types/fork_id.rs b/crates/common/types/fork_id.rs index c7fa3ff40d1..e51faffe85f 100644 --- a/crates/common/types/fork_id.rs +++ b/crates/common/types/fork_id.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crc32fast::Hasher; use ethrex_rlp::{ decode::RLPDecode, @@ -20,6 +22,12 @@ pub struct ForkId { pub fork_next: BlockNumber, } +impl fmt::Display for ForkId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.fork_hash, self.fork_next) + } +} + impl ForkId { pub fn new( chain_config: ChainConfig, diff --git a/crates/networking/p2p/discv4/peer_table.rs b/crates/networking/p2p/discv4/peer_table.rs index 1b20880fc26..30c26c6f81c 100644 --- a/crates/networking/p2p/discv4/peer_table.rs +++ b/crates/networking/p2p/discv4/peer_table.rs @@ -58,6 +58,8 @@ pub struct Contact { pub knows_us: bool, // This is a known-bad peer (on another network, no matching capabilities, etc) pub unwanted: bool, + /// Whether the last known fork ID is valid, None if unknown. + pub is_fork_id_valid: Option, } impl Contact { @@ -106,6 +108,7 @@ impl From for Contact { disposable: false, knows_us: true, unwanted: false, + is_fork_id_valid: None, } } } @@ -221,6 +224,21 @@ impl PeerTable { Ok(()) } + /// Set whether the contact fork id is valid. + pub async fn set_is_fork_id_valid( + &mut self, + node_id: &H256, + valid: bool, + ) -> Result<(), PeerTableError> { + self.handle + .cast(CastMessage::SetIsForkIdValid { + node_id: *node_id, + valid, + }) + .await?; + Ok(()) + } + /// Record a successful connection, used to score peers pub async fn record_success(&mut self, node_id: &H256) -> Result<(), PeerTableError> { self.handle @@ -662,7 +680,11 @@ impl PeerTableServer { fn get_contact_for_lookup(&self) -> Option { self.contacts .values() - .filter(|c| c.n_find_node_sent < MAX_FIND_NODE_PER_PEER && !c.disposable) + .filter(|c| { + c.n_find_node_sent < MAX_FIND_NODE_PER_PEER + && !c.disposable + && c.is_fork_id_valid != Some(false) + }) .collect::>() .choose(&mut rand::rngs::OsRng) .cloned() @@ -847,6 +869,10 @@ enum CastMessage { SetUnwanted { node_id: H256, }, + SetIsForkIdValid { + node_id: H256, + valid: bool, + }, RecordSuccess { node_id: H256, }, @@ -1099,6 +1125,11 @@ impl GenServer for PeerTableServer { .entry(node_id) .and_modify(|contact| contact.unwanted = true); } + CastMessage::SetIsForkIdValid { node_id, valid } => { + self.contacts + .entry(node_id) + .and_modify(|contact| contact.is_fork_id_valid = Some(valid)); + } CastMessage::RecordSuccess { node_id } => { self.peers .entry(node_id) diff --git a/crates/networking/p2p/discv4/server.rs b/crates/networking/p2p/discv4/server.rs index 55f83e1bf4d..6207a8f3700 100644 --- a/crates/networking/p2p/discv4/server.rs +++ b/crates/networking/p2p/discv4/server.rs @@ -14,8 +14,8 @@ use crate::{ }, }; use bytes::BytesMut; -use ethrex_common::{H256, H512}; -use ethrex_storage::Store; +use ethrex_common::{H256, H512, types::ForkId}; +use ethrex_storage::{Store, error::StoreError}; use futures::StreamExt; use rand::rngs::OsRng; use secp256k1::SecretKey; @@ -59,6 +59,8 @@ pub enum DiscoveryServerError { InvalidContact, #[error(transparent)] PeerTable(#[from] PeerTableError), + #[error(transparent)] + Store(#[from] StoreError), } #[derive(Debug, Clone)] @@ -83,6 +85,7 @@ pub struct DiscoveryServer { local_node_record: NodeRecord, signer: SecretKey, udp_socket: Arc, + store: Store, peer_table: PeerTable, /// The last `FindNode` message sent, cached due to message /// signatures being expensive. @@ -113,6 +116,7 @@ impl DiscoveryServer { local_node_record, signer, udp_socket, + store: storage.clone(), peer_table: peer_table.clone(), find_node_message: Self::random_message(&signer), }; @@ -342,7 +346,7 @@ impl DiscoveryServer { .expect("first 32 bytes are the message hash"); // We do not use self.send() here, as we already encoded the message to calculate hash. self.udp_socket.send_to(&buf, node.udp_addr()).await?; - debug!(sent = "Ping", to = %format!("{:#x}", node.public_key)); + trace!(sent = "Ping", to = %format!("{:#x}", node.public_key)); Ok(H256::from(ping_hash)) } @@ -362,7 +366,7 @@ impl DiscoveryServer { self.send(pong, node.udp_addr()).await?; - debug!(sent = "Pong", to = %format!("{:#x}", node.public_key)); + trace!(sent = "Pong", to = %format!("{:#x}", node.public_key)); Ok(()) } @@ -501,10 +505,68 @@ impl DiscoveryServer { .record_enr_response_received( &node_id, enr_response_message.request_hash, - enr_response_message.node_record, + enr_response_message.node_record.clone(), ) .await?; + self.validate_enr_fork_id(node_id, sender_public_key, enr_response_message.node_record) + .await?; + + Ok(()) + } + + /// Validates the fork id of the given ENR is valid, saving it to the peer_table. + async fn validate_enr_fork_id( + &mut self, + node_id: H256, + sender_public_key: H512, + node_record: NodeRecord, + ) -> Result<(), DiscoveryServerError> { + let pairs = node_record.decode_pairs(); + + let Some(remote_fork_id) = pairs.eth else { + self.peer_table + .set_is_fork_id_valid(&node_id, false) + .await?; + debug!(received = "ENRResponse", from = %format!("{sender_public_key:#x}"), "missing fork id in ENR response, skipping"); + return Ok(()); + }; + + let chain_config = self.store.get_chain_config(); + let genesis_header = self + .store + .get_block_header(0)? + .ok_or(DiscoveryServerError::InvalidContact)?; + let latest_block_number = self.store.get_latest_block_number().await?; + let latest_block_header = self + .store + .get_block_header(latest_block_number)? + .ok_or(DiscoveryServerError::InvalidContact)?; + + let local_fork_id = ForkId::new( + chain_config, + genesis_header.clone(), + latest_block_header.timestamp, + latest_block_number, + ); + + if !local_fork_id.is_valid( + remote_fork_id.clone(), + latest_block_number, + latest_block_header.timestamp, + chain_config, + genesis_header, + ) { + self.peer_table + .set_is_fork_id_valid(&node_id, false) + .await?; + debug!(received = "ENRResponse", from = %format!("{sender_public_key:#x}"), local_fork_id=%local_fork_id, remote_fork_id=%remote_fork_id, "fork id mismatch in ENR response, skipping"); + return Ok(()); + } + + debug!(received = "ENRResponse", from = %format!("{sender_public_key:#x}"), local_fork_id=%local_fork_id, remote_fork_id=%remote_fork_id, "valid fork id in ENR found"); + self.peer_table.set_is_fork_id_valid(&node_id, true).await?; + Ok(()) }