diff --git a/crates/networking/p2p/discv4/server.rs b/crates/networking/p2p/discv4/server.rs index 02cb4f94fea..957352cad28 100644 --- a/crates/networking/p2p/discv4/server.rs +++ b/crates/networking/p2p/discv4/server.rs @@ -15,6 +15,7 @@ use crate::{ }; use bytes::BytesMut; use ethrex_common::{H256, H512}; +use ethrex_storage::Store; use futures::StreamExt; use rand::rngs::OsRng; use secp256k1::SecretKey; @@ -90,6 +91,7 @@ pub struct DiscoveryServer { impl DiscoveryServer { pub async fn spawn( + storage: Store, local_node: Node, signer: SecretKey, udp_socket: Arc, @@ -98,8 +100,14 @@ impl DiscoveryServer { ) -> Result<(), DiscoveryServerError> { info!("Starting Discovery Server"); - let local_node_record = NodeRecord::from_node(&local_node, 1, &signer) + let mut local_node_record = NodeRecord::from_node(&local_node, 1, &signer) .expect("Failed to create local node record"); + if let Ok(fork_id) = storage.get_fork_id().await { + local_node_record + .set_fork_id(fork_id, &signer) + .expect("Failed to set fork_id on local node record"); + } + let mut discovery_server = Self { local_node: local_node.clone(), local_node_record, diff --git a/crates/networking/p2p/network.rs b/crates/networking/p2p/network.rs index b7bd8324313..55574e18d99 100644 --- a/crates/networking/p2p/network.rs +++ b/crates/networking/p2p/network.rs @@ -111,6 +111,7 @@ pub async fn start_network(context: P2PContext, bootnodes: Vec) -> Result< ); DiscoveryServer::spawn( + context.storage.clone(), context.local_node.clone(), context.signer, udp_socket.clone(), diff --git a/crates/networking/p2p/types.rs b/crates/networking/p2p/types.rs index 0196e8834c0..2073db1d7d5 100644 --- a/crates/networking/p2p/types.rs +++ b/crates/networking/p2p/types.rs @@ -271,6 +271,7 @@ pub struct NodeRecord { pub seq: u64, // holds optional values in (key, value) format // value represents the rlp encoded bytes + // The key/value pairs must be sorted by key and must be unique pub pairs: Vec<(Bytes, Bytes)>, } @@ -377,6 +378,23 @@ impl NodeRecord { Ok(record) } + pub fn set_fork_id(&mut self, fork_id: ForkId, signer: &SecretKey) -> Result<(), NodeError> { + // Without the Vec wrapper, RLP encoding fork_id directly would produce: + // [forkHash, forkNext] + // But the spec requires nested lists: + // [[forkHash, forkNext]] + let eth = vec![fork_id]; + self.pairs.push(("eth".into(), eth.encode_to_vec().into())); + + //Pairs need to be sorted by their key. + //The keys are Bytes which implements Ord, so they can be compared directly. The sorting + //will be lexicographic (alphabetical for string keys like "eth", "id", "ip", etc.). + self.pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + self.signature = self.sign_record(signer)?; + Ok(()) + } + fn sign_record(&self, signer: &SecretKey) -> Result { let digest = &self.get_signature_digest(); let msg = secp256k1::Message::from_digest_slice(digest) @@ -402,6 +420,11 @@ impl From for Vec<(Bytes, Bytes)> { fn from(value: NodeRecordPairs) -> Self { let mut pairs = vec![]; if let Some(eth) = value.eth { + // Without the Vec wrapper, RLP encoding fork_id directly would produce: + // [forkHash, forkNext] + // But the spec requires nested lists: + // [[forkHash, forkNext]] + let eth = vec![eth]; pairs.push(("eth".into(), eth.encode_to_vec().into())); } if let Some(id) = value.id { @@ -500,6 +523,7 @@ mod tests { utils::public_key_from_signing_key, }; use ethrex_common::H512; + use ethrex_rlp::decode::RLPDecode; use ethrex_storage::{EngineType, Store}; use secp256k1::SecretKey; use std::{net::SocketAddr, str::FromStr}; @@ -590,4 +614,41 @@ mod tests { assert_eq!(record.enr_url().unwrap(), expected_enr_string); } + + #[tokio::test] + async fn encode_decode_node_record_with_forkid() { + let signer = SecretKey::from_slice(&[ + 16, 125, 177, 238, 167, 212, 168, 215, 239, 165, 77, 224, 199, 143, 55, 205, 9, 194, + 87, 139, 92, 46, 30, 191, 74, 37, 68, 242, 38, 225, 104, 246, + ]) + .unwrap(); + let addr = std::net::SocketAddr::from_str("127.0.0.1:30303").unwrap(); + + let mut storage = + Store::new("", EngineType::InMemory).expect("Failed to create in-memory storage"); + storage + .add_initial_state(serde_json::from_str(TEST_GENESIS).unwrap()) + .await + .expect("Failed to build test genesis"); + + let node = Node::new( + addr.ip(), + addr.port(), + addr.port(), + public_key_from_signing_key(&signer), + ); + let fork_id = storage.get_fork_id().await.unwrap(); + + let mut record = NodeRecord::from_node(&node, 1, &signer).unwrap(); + record.set_fork_id(fork_id.clone(), &signer).unwrap(); + + record.sign_record(&signer).unwrap(); + + let enr_url = record.enr_url().unwrap(); + let base64_decoded = ethrex_common::base64::decode(&enr_url.as_bytes()[4..]); + let parsed_record = NodeRecord::decode(&base64_decoded).unwrap(); + let pairs = parsed_record.decode_pairs(); + + assert_eq!(pairs.eth, Some(fork_id)); + } }