From 9cca7a6b35dc488ceef2d12623cb2ddc6b00140b Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 16 Jun 2026 12:28:23 +0300 Subject: [PATCH 1/9] p2p: compare peers ports to allow running multiple nodes from single IP --- p2p/src/types.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/p2p/src/types.rs b/p2p/src/types.rs index 8d0bdd1a5..f748aa6fa 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -219,14 +219,9 @@ impl std::hash::Hash for PeerAddr { } impl PartialEq for PeerAddr { - /// If loopback address then we care about ip and port. - /// If regular address then we only care about the ip and ignore the port. + /// We care about ip and port for IP address. fn eq(&self, other: &PeerAddr) -> bool { - if self.0.ip().is_loopback() { - self.0 == other.0 - } else { - self.0.ip() == other.0.ip() - } + self.0 == other.0 } } From 07d9051d6c1f1b843f42cd55c97e5ebd88de2b97 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 16 Jun 2026 14:51:10 +0300 Subject: [PATCH 2/9] p2p: allow multiple peers with same IP address if belongs to private network --- p2p/src/types.rs | 100 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/p2p/src/types.rs b/p2p/src/types.rs index f748aa6fa..de5c791cd 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -207,10 +207,10 @@ impl<'de> Deserialize<'de> for PeerAddrs { } impl std::hash::Hash for PeerAddr { - /// If loopback address then we care about ip and port. + /// If private address then we care about ip and port. /// If regular address then we only care about the ip and ignore the port. fn hash(&self, state: &mut H) { - if self.0.ip().is_loopback() { + if is_private_ip(&self.0.ip()) { self.0.hash(state); } else { self.0.ip().hash(state); @@ -219,12 +219,69 @@ impl std::hash::Hash for PeerAddr { } impl PartialEq for PeerAddr { - /// We care about ip and port for IP address. + /// If private address then we care about ip and port. + /// If regular address then we only care about the ip and ignore the port. fn eq(&self, other: &PeerAddr) -> bool { - self.0 == other.0 + if is_private_ip(&self.0.ip()) { + self.0 == other.0 + } else { + self.0.ip() == other.0.ip() + } } } +/// Check if IP address is private. +/// Implementation taken from `core::net:ip_addr` while `is_global` is unstable. +fn is_private_ip(ip: &IpAddr) -> bool { + let shared = match ip { + IpAddr::V4(ip) => ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000), + IpAddr::V6(ip) => ip.is_loopback(), + }; + let private = match ip { + IpAddr::V4(ip) => { + ip.is_private() || ip.is_loopback() || ip.is_link_local() || ip.is_documentation() + // addresses reserved for future protocols (`192.0.0.0/24`) + // .9 and .10 are documented as globally reachable so they're excluded + || ( + ip.octets()[0] == 192 && ip.octets()[1] == 0 && ip.octets()[2] == 0 + && ip.octets()[3] != 9 && ip.octets()[3] != 10 + ) + } + IpAddr::V6(ip) => { + ip.is_loopback() || ip.is_unspecified() + // IPv4-mapped Address (`::ffff:0:0/96`) + || matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _]) + // IPv4-IPv6 Translat. (`64:ff9b:1::/48`) + || matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _]) + // Discard-Only Address Block (`100::/64`) + || matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _]) + // IETF Protocol Assignments (`2001::/23`) + || (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200) + && !( + // Port Control Protocol Anycast (`2001:1::1`) + u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001 + // Traversal Using Relays around NAT Anycast (`2001:1::2`) + || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002 + // AMT (`2001:3::/32`) + || matches!(ip.segments(), [0x2001, 3, _, _, _, _, _, _]) + // AS112-v6 (`2001:4:112::/48`) + || matches!(ip.segments(), [0x2001, 4, 0x112, _, _, _, _, _]) + // ORCHIDv2 (`2001:20::/28`) + // Drone Remote ID Protocol Entity Tags (DETs) Prefix (`2001:30::/28`)` + || matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x3F) + )) + // 6to4 (`2002::/16`) – it's not explicitly documented as globally reachable, + // IANA says N/A. + || matches!(ip.segments(), [0x2002, _, _, _, _, _, _, _]) + // Segment Routing (SRv6) SIDs (`5f00::/16`) + || matches!(ip.segments(), [0x5f00, ..]) + || ip.is_unique_local() + || ip.is_unicast_link_local() + } + }; + shared || private +} + impl Eq for PeerAddr {} impl std::fmt::Display for PeerAddr { @@ -241,10 +298,10 @@ impl PeerAddr { PeerAddr(SocketAddr::new(addr, port)) } - /// If the ip is loopback then our key is "ip:port" (mainly for local usernet testing). - /// Otherwise we only care about the ip (we disallow multiple peers on the same ip address). + /// If the ip is private then our key is "ip:port". + /// Otherwise, we only care about the ip (we disallow multiple peers on the same ip address). pub fn as_key(&self) -> String { - if self.0.ip().is_loopback() { + if is_private_ip(&self.0.ip()) { format!("{}:{}", self.0.ip(), self.0.port()) } else { format!("{}", self.0.ip()) @@ -312,42 +369,31 @@ impl Default for P2PConfig { impl P2PConfig { /// return ban window pub fn ban_window(&self) -> i64 { - match self.ban_window { - Some(n) => n, - None => BAN_WINDOW, - } + self.ban_window.unwrap_or_else(|| BAN_WINDOW) } /// return maximum inbound peer connections count pub fn peer_max_inbound_count(&self) -> u32 { - match self.peer_max_inbound_count { - Some(n) => n, - None => PEER_MAX_INBOUND_COUNT, - } + self.peer_max_inbound_count + .unwrap_or_else(|| PEER_MAX_INBOUND_COUNT) } /// return maximum outbound peer connections count pub fn peer_max_outbound_count(&self) -> u32 { - match self.peer_max_outbound_count { - Some(n) => n, - None => PEER_MAX_OUTBOUND_COUNT, - } + self.peer_max_outbound_count + .unwrap_or_else(|| PEER_MAX_OUTBOUND_COUNT) } /// return minimum preferred outbound peer count pub fn peer_min_preferred_outbound_count(&self) -> u32 { - match self.peer_min_preferred_outbound_count { - Some(n) => n, - None => PEER_MIN_PREFERRED_OUTBOUND_COUNT, - } + self.peer_min_preferred_outbound_count + .unwrap_or_else(|| PEER_MIN_PREFERRED_OUTBOUND_COUNT) } /// return peer buffer count for listener pub fn peer_listener_buffer_count(&self) -> u32 { - match self.peer_listener_buffer_count { - Some(n) => n, - None => PEER_LISTENER_BUFFER_COUNT, - } + self.peer_listener_buffer_count + .unwrap_or_else(|| PEER_LISTENER_BUFFER_COUNT) } } From a6a6430ed64b188071b686d7a807adec8f645cbc Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 16 Jun 2026 15:46:08 +0300 Subject: [PATCH 3/9] p2p: fix peers hash tests, move peers ports to constants --- config/src/config.rs | 36 +++++++++++++++--------------------- p2p/src/types.rs | 23 ++++++++++++++++------- p2p/tests/peer_addr.rs | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/config/src/config.rs b/config/src/config.rs index 716064904..bd26b2317 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -22,6 +22,8 @@ use std::io::prelude::*; use std::io::BufReader; use std::path::PathBuf; +use p2p::types::{TESTNET_PEER_PORT, USERNET_PEER_PORT}; + use crate::comments::insert_comments; use crate::core::global; use crate::p2p; @@ -42,10 +44,7 @@ pub const FOREIGN_API_SECRET_FILE_NAME: &str = ".foreign_api_secret"; fn get_grin_path(chain_type: &global::ChainTypes) -> Result { // Check if grin dir exists - let mut grin_path = match dirs::home_dir() { - Some(p) => p, - None => PathBuf::new(), - }; + let mut grin_path = dirs::home_dir().unwrap_or_else(|| PathBuf::new()); grin_path.push(GRIN_HOME); grin_path.push(chain_type.shortname()); // Create if the default path doesn't exist @@ -113,7 +112,7 @@ fn check_api_secret_files( pub fn initial_setup_server(chain_type: &global::ChainTypes) -> Result { check_api_secret_files(chain_type, API_SECRET_FILE_NAME)?; check_api_secret_files(chain_type, FOREIGN_API_SECRET_FILE_NAME)?; - // Use config file if current directory if it exists, .grin home otherwise + // Use config file in current directory if it exists, .grin home otherwise if let Some(p) = check_config_current_dir(SERVER_CONFIG_FILE_NAME) { GlobalConfig::new(p.to_str().unwrap()) } else { @@ -168,7 +167,7 @@ impl GlobalConfig { global::ChainTypes::Mainnet => {} global::ChainTypes::Testnet => { defaults.api_http_addr = "127.0.0.1:13413".to_owned(); - defaults.p2p_config.port = 13414; + defaults.p2p_config.port = TESTNET_PEER_PORT; defaults .stratum_mining_config .as_mut() @@ -182,7 +181,7 @@ impl GlobalConfig { } global::ChainTypes::UserTesting => { defaults.api_http_addr = "127.0.0.1:23413".to_owned(); - defaults.p2p_config.port = 23414; + defaults.p2p_config.port = USERNET_PEER_PORT; defaults.p2p_config.seeding_type = p2p::Seeding::None; defaults .stratum_mining_config @@ -234,14 +233,12 @@ impl GlobalConfig { match decoded { Ok(gc) => { self.members = Some(gc); - return Ok(self); - } - Err(e) => { - return Err(ConfigError::ParseError( - self.config_file_path.unwrap().to_str().unwrap().to_string(), - format!("{}", e), - )); + Ok(self) } + Err(e) => Err(ConfigError::ParseError( + self.config_file_path.unwrap().to_str().unwrap().to_string(), + format!("{}", e), + )), } } @@ -275,8 +272,7 @@ impl GlobalConfig { /// Enable mining pub fn stratum_enabled(&mut self) -> bool { - return self - .members + self.members .as_mut() .unwrap() .server @@ -284,7 +280,7 @@ impl GlobalConfig { .as_mut() .unwrap() .enable_stratum_server - .unwrap(); + .unwrap() } /// Serialize config @@ -292,10 +288,8 @@ impl GlobalConfig { let encoded: Result = toml::to_string(self.members.as_mut().unwrap()); match encoded { - Ok(enc) => return Ok(enc), - Err(e) => { - return Err(ConfigError::SerializationError(format!("{}", e))); - } + Ok(enc) => Ok(enc), + Err(e) => Err(ConfigError::SerializationError(format!("{}", e))), } } diff --git a/p2p/src/types.rs b/p2p/src/types.rs index de5c791cd..aba10d466 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -37,6 +37,13 @@ use crate::msg::PeerAddrs; use crate::util::secp::pedersen::RangeProof; use crate::util::RwLock; +/// Default main network peer port. +pub const MAINNET_PEER_PORT: u16 = 3414; +/// Default test network peer port. +pub const TESTNET_PEER_PORT: u16 = 13414; +/// Default user network peer port. +pub const USERNET_PEER_PORT: u16 = 23414; + /// Maximum number of block headers a peer should ever send pub const MAX_BLOCK_HEADERS: u32 = 512; @@ -284,17 +291,19 @@ fn is_private_ip(ip: &IpAddr) -> bool { impl Eq for PeerAddr {} -impl std::fmt::Display for PeerAddr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl fmt::Display for PeerAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } - impl PeerAddr { - /// Convenient way of constructing a new peer_addr from an ip_addr - /// defaults to port 3414 on mainnet and 13414 on testnet. + /// Convenient way of constructing a new peer address from an ip address. pub fn from_ip(addr: IpAddr) -> PeerAddr { - let port = if global::is_testnet() { 13414 } else { 3414 }; + let port = if global::is_testnet() { + TESTNET_PEER_PORT + } else { + MAINNET_PEER_PORT + }; PeerAddr(SocketAddr::new(addr, port)) } @@ -348,7 +357,7 @@ impl Default for P2PConfig { let ipaddr = "::".parse().unwrap(); P2PConfig { host: ipaddr, - port: 3414, + port: MAINNET_PEER_PORT, seeding_type: Seeding::default(), seeds: None, peers_allow: None, diff --git a/p2p/tests/peer_addr.rs b/p2p/tests/peer_addr.rs index 645d2c8e2..597775ba8 100644 --- a/p2p/tests/peer_addr.rs +++ b/p2p/tests/peer_addr.rs @@ -24,17 +24,17 @@ use crate::p2p::types::PeerAddr; fn test_peer_addr_hashing() { let mut peers: HashMap = HashMap::new(); - let socket_addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), 8080); + let socket_addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(185, 147, 152, 14)), 8080); let peer_addr1 = PeerAddr(socket_addr1); peers.insert(peer_addr1, "peer1".into()); assert!(peers.contains_key(&peer_addr1)); assert_eq!(peers.len(), 1); - let socket_addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), 8081); + let socket_addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(185, 147, 152, 14)), 8081); let peer_addr2 = PeerAddr(socket_addr2); - // Expected behavior here is to ignore the port when hashing peer_addr. + // Expected behavior here is to ignore the port when hashing non-private peer_addr. // This means the two peer_addr instances above are seen as the same addr. assert!(peers.contains_key(&peer_addr1)); assert!(peers.contains_key(&peer_addr2)); @@ -52,4 +52,31 @@ fn test_peer_addr_hashing() { assert_eq!(peer_addr2.0, socket_addr2); assert_eq!(peer_addr1.0.port(), 8080); assert_eq!(peer_addr2.0.port(), 8081); + + peers = HashMap::new(); + + let socket_addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 8080); + let peer_addr1 = PeerAddr(socket_addr1); + peers.insert(peer_addr1, "peer1".into()); + + assert!(peers.contains_key(&peer_addr1)); + assert_eq!(peers.len(), 1); + + let socket_addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 8081); + let peer_addr2 = PeerAddr(socket_addr2); + + // Expected behavior here is to not ignore the port when hashing private peer_addr. + // This means the two peer_addr instances above are seen as not the same addr. + assert!(peers.contains_key(&peer_addr1)); + assert!(!peers.contains_key(&peer_addr2)); + + peers.insert(peer_addr2, "peer2".into()); + + // Inserting the second instance is a valid operation as they are treated as not the same addr. + assert!(peers.contains_key(&peer_addr1)); + assert!(peers.contains_key(&peer_addr2)); + assert_eq!(peers.len(), 2); + + // Check they are treated as not the same even though their underlying ports are different. + assert_ne!(peer_addr1, peer_addr2); } From 108e52d92a582af03796c208292f4013da772885 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 16 Jun 2026 16:15:42 +0300 Subject: [PATCH 4/9] p2p: check shared ip only for v4 --- p2p/src/types.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/p2p/src/types.rs b/p2p/src/types.rs index aba10d466..8634c0181 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -240,11 +240,7 @@ impl PartialEq for PeerAddr { /// Check if IP address is private. /// Implementation taken from `core::net:ip_addr` while `is_global` is unstable. fn is_private_ip(ip: &IpAddr) -> bool { - let shared = match ip { - IpAddr::V4(ip) => ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000), - IpAddr::V6(ip) => ip.is_loopback(), - }; - let private = match ip { + match ip { IpAddr::V4(ip) => { ip.is_private() || ip.is_loopback() || ip.is_link_local() || ip.is_documentation() // addresses reserved for future protocols (`192.0.0.0/24`) @@ -252,6 +248,9 @@ fn is_private_ip(ip: &IpAddr) -> bool { || ( ip.octets()[0] == 192 && ip.octets()[1] == 0 && ip.octets()[2] == 0 && ip.octets()[3] != 9 && ip.octets()[3] != 10 + // this address is part of the Shared Address Space defined in + // [IETF RFC 6598] (`100.64.0.0/10`). + || ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000) ) } IpAddr::V6(ip) => { @@ -285,8 +284,7 @@ fn is_private_ip(ip: &IpAddr) -> bool { || ip.is_unique_local() || ip.is_unicast_link_local() } - }; - shared || private + } } impl Eq for PeerAddr {} From c3b01e84b0231f97f699b19c922e95c0d4b8632b Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 16 Jun 2026 18:55:29 +0300 Subject: [PATCH 5/9] p2p: generate random ports --- config/src/config.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/config/src/config.rs b/config/src/config.rs index bd26b2317..c5100cd25 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -14,16 +14,18 @@ //! Configuration file management +use grin_p2p::types::MAINNET_PEER_PORT; +use grin_util::secp::rand::Rng; +use p2p::types::{TESTNET_PEER_PORT, USERNET_PEER_PORT}; use rand::distributions::{Alphanumeric, Distribution}; use rand::thread_rng; use std::env; use std::fs::{self, File}; use std::io::prelude::*; use std::io::BufReader; +use std::net::TcpListener; use std::path::PathBuf; -use p2p::types::{TESTNET_PEER_PORT, USERNET_PEER_PORT}; - use crate::comments::insert_comments; use crate::core::global; use crate::p2p; @@ -164,10 +166,12 @@ impl GlobalConfig { defaults.chain_type = chain_type.clone(); match *chain_type { - global::ChainTypes::Mainnet => {} + global::ChainTypes::Mainnet => { + defaults.p2p_config.port = Self::p2p_port_for_chain(chain_type); + } global::ChainTypes::Testnet => { defaults.api_http_addr = "127.0.0.1:13413".to_owned(); - defaults.p2p_config.port = TESTNET_PEER_PORT; + defaults.p2p_config.port = Self::p2p_port_for_chain(chain_type); defaults .stratum_mining_config .as_mut() @@ -181,7 +185,7 @@ impl GlobalConfig { } global::ChainTypes::UserTesting => { defaults.api_http_addr = "127.0.0.1:23413".to_owned(); - defaults.p2p_config.port = USERNET_PEER_PORT; + defaults.p2p_config.port = Self::p2p_port_for_chain(chain_type); defaults.p2p_config.seeding_type = p2p::Seeding::None; defaults .stratum_mining_config @@ -357,6 +361,26 @@ impl GlobalConfig { .replace("WARN", "Warning") .replace("ERROR", "Error") } + + /// Generate random p2p port for provided chain type. + /// Return default after 5 unsuccessful attempts. + fn p2p_port_for_chain(chain_type: &global::ChainTypes) -> u16 { + let default_host = "::"; + for _ in 0..5 { + let port = thread_rng().gen_range(1024, 49151); + match TcpListener::bind((default_host, port)) { + Ok(_) => { + return port; + } + Err(_) => continue, + }; + } + match chain_type { + global::ChainTypes::Testnet => TESTNET_PEER_PORT, + global::ChainTypes::Mainnet => MAINNET_PEER_PORT, + _ => USERNET_PEER_PORT, + } + } } #[test] From 7c1a0f6565ea0f1e2c0e711441d3f8f0e40759b3 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 16 Jun 2026 20:57:38 +0300 Subject: [PATCH 6/9] Revert "p2p: generate random ports" This reverts commit c3b01e84b0231f97f699b19c922e95c0d4b8632b. --- config/src/config.rs | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/config/src/config.rs b/config/src/config.rs index c5100cd25..bd26b2317 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -14,18 +14,16 @@ //! Configuration file management -use grin_p2p::types::MAINNET_PEER_PORT; -use grin_util::secp::rand::Rng; -use p2p::types::{TESTNET_PEER_PORT, USERNET_PEER_PORT}; use rand::distributions::{Alphanumeric, Distribution}; use rand::thread_rng; use std::env; use std::fs::{self, File}; use std::io::prelude::*; use std::io::BufReader; -use std::net::TcpListener; use std::path::PathBuf; +use p2p::types::{TESTNET_PEER_PORT, USERNET_PEER_PORT}; + use crate::comments::insert_comments; use crate::core::global; use crate::p2p; @@ -166,12 +164,10 @@ impl GlobalConfig { defaults.chain_type = chain_type.clone(); match *chain_type { - global::ChainTypes::Mainnet => { - defaults.p2p_config.port = Self::p2p_port_for_chain(chain_type); - } + global::ChainTypes::Mainnet => {} global::ChainTypes::Testnet => { defaults.api_http_addr = "127.0.0.1:13413".to_owned(); - defaults.p2p_config.port = Self::p2p_port_for_chain(chain_type); + defaults.p2p_config.port = TESTNET_PEER_PORT; defaults .stratum_mining_config .as_mut() @@ -185,7 +181,7 @@ impl GlobalConfig { } global::ChainTypes::UserTesting => { defaults.api_http_addr = "127.0.0.1:23413".to_owned(); - defaults.p2p_config.port = Self::p2p_port_for_chain(chain_type); + defaults.p2p_config.port = USERNET_PEER_PORT; defaults.p2p_config.seeding_type = p2p::Seeding::None; defaults .stratum_mining_config @@ -361,26 +357,6 @@ impl GlobalConfig { .replace("WARN", "Warning") .replace("ERROR", "Error") } - - /// Generate random p2p port for provided chain type. - /// Return default after 5 unsuccessful attempts. - fn p2p_port_for_chain(chain_type: &global::ChainTypes) -> u16 { - let default_host = "::"; - for _ in 0..5 { - let port = thread_rng().gen_range(1024, 49151); - match TcpListener::bind((default_host, port)) { - Ok(_) => { - return port; - } - Err(_) => continue, - }; - } - match chain_type { - global::ChainTypes::Testnet => TESTNET_PEER_PORT, - global::ChainTypes::Mainnet => MAINNET_PEER_PORT, - _ => USERNET_PEER_PORT, - } - } } #[test] From 1ed86de3d99394c44091737f97eabb10c95460ca Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 16 Jun 2026 20:59:06 +0300 Subject: [PATCH 7/9] p2p: do not store private IPs peers --- p2p/src/peers.rs | 27 +++++++++++++++++++++------ p2p/src/store.rs | 9 ++++++++- p2p/src/types.rs | 2 +- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/p2p/src/peers.rs b/p2p/src/peers.rs index e62a2724f..3b3ade719 100644 --- a/p2p/src/peers.rs +++ b/p2p/src/peers.rs @@ -32,8 +32,8 @@ use crate::msg::PeerAddrs; use crate::peer::Peer; use crate::store::{PeerData, PeerStore, State}; use crate::types::{ - Capabilities, ChainAdapter, Error, NetAdapter, P2PConfig, PeerAddr, PeerInfo, ReasonForBan, - TxHashSetRead, MAX_PEER_ADDRS, + is_private_ip, Capabilities, ChainAdapter, Error, NetAdapter, P2PConfig, PeerAddr, PeerInfo, + ReasonForBan, TxHashSetRead, MAX_PEER_ADDRS, }; use crate::util::secp::pedersen::RangeProof; use chrono::prelude::*; @@ -86,9 +86,14 @@ impl Peers { peers.insert(peer_data.addr, peer); } } - debug!("Saving newly connected peer {}.", peer_data.addr); - if let Err(e) = self.save_peer(&peer_data) { - error!("Could not save connected peer address: {:?}", e); + // Do not save private peer. + if !is_private_ip(&peer_data.addr.0.ip()) { + debug!("Saving newly connected peer {}.", peer_data.addr); + if let Err(e) = self.save_peer(&peer_data) { + error!("Could not save connected peer address: {:?}", e); + } + } else { + debug!("Do not save connected private peer {}.", peer_data.addr); } Ok(()) } @@ -180,6 +185,12 @@ impl Peers { // check if peer exist self.get_peer(peer_addr)?; if self.is_banned(peer_addr) { + // delete banned private peer + if is_private_ip(&peer_addr.0.ip()) { + if let Ok(mut batch) = self.store.iter_batch() { + batch.delete_peer(peer_addr)?; + } + } self.update_state(peer_addr, State::Healthy) } else { Err(Error::PeerNotBanned) @@ -328,7 +339,11 @@ impl Peers { /// Saves updated information about mulitple peers in batch pub fn save_peers(&self, p: Vec) -> Result<(), Error> { - self.store.save_peers(p).map_err(From::from) + let public_peers = p + .iter() + .filter(|p| !is_private_ip(&p.addr.0.ip())) + .collect::>(); + self.store.save_peers(public_peers).map_err(From::from) } /// Updates the state of a peer in store diff --git a/p2p/src/store.rs b/p2p/src/store.rs index 675d31b8b..f301187ab 100644 --- a/p2p/src/store.rs +++ b/p2p/src/store.rs @@ -138,7 +138,7 @@ impl PeerStore { batch.commit() } - pub fn save_peers(&self, p: Vec) -> Result<(), Error> { + pub fn save_peers(&self, p: Vec<&PeerData>) -> Result<(), Error> { let mut batch = self.db.batch()?; for pd in p { debug!("save_peers: {:?} marked {:?}", pd.addr, pd.flags); @@ -233,6 +233,13 @@ impl<'a> PeersIterBatch<'a> { Ok(peers) } + /// Delete a peer by provided address. + pub fn delete_peer(&mut self, peer_addr: PeerAddr) -> Result<(), Error> { + let key = peer_addr.as_key(); + self.db.delete(Some(PEER_PREFIX), key.as_bytes())?; + Ok(()) + } + /// Deletes peers from the storage that satisfy some condition `predicate` pub fn delete_peers(mut self, predicate: F) -> Result<(), Error> where diff --git a/p2p/src/types.rs b/p2p/src/types.rs index 8634c0181..1bccc8c4c 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -239,7 +239,7 @@ impl PartialEq for PeerAddr { /// Check if IP address is private. /// Implementation taken from `core::net:ip_addr` while `is_global` is unstable. -fn is_private_ip(ip: &IpAddr) -> bool { +pub fn is_private_ip(ip: &IpAddr) -> bool { match ip { IpAddr::V4(ip) => { ip.is_private() || ip.is_loopback() || ip.is_link_local() || ip.is_documentation() From da70d2f854e8ae0dac636ec32d096ab3121e6579 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 17 Jun 2026 13:27:55 +0300 Subject: [PATCH 8/9] p2p: do not update state for deleted private peer --- p2p/src/peers.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/src/peers.rs b/p2p/src/peers.rs index 3b3ade719..9758051d9 100644 --- a/p2p/src/peers.rs +++ b/p2p/src/peers.rs @@ -187,11 +187,11 @@ impl Peers { if self.is_banned(peer_addr) { // delete banned private peer if is_private_ip(&peer_addr.0.ip()) { - if let Ok(mut batch) = self.store.iter_batch() { - batch.delete_peer(peer_addr)?; - } + let batch = self.store.iter_batch()?; + batch.delete_peer(peer_addr).map_err(|e| Error::Store(e)) + } else { + self.update_state(peer_addr, State::Healthy) } - self.update_state(peer_addr, State::Healthy) } else { Err(Error::PeerNotBanned) } From c377be29034bae08dd3681869451816d271b8165 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 17 Jun 2026 13:28:53 +0300 Subject: [PATCH 9/9] p2p: commit peer deletion --- p2p/src/store.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/src/store.rs b/p2p/src/store.rs index f301187ab..72ef25863 100644 --- a/p2p/src/store.rs +++ b/p2p/src/store.rs @@ -234,9 +234,10 @@ impl<'a> PeersIterBatch<'a> { } /// Delete a peer by provided address. - pub fn delete_peer(&mut self, peer_addr: PeerAddr) -> Result<(), Error> { + pub fn delete_peer(mut self, peer_addr: PeerAddr) -> Result<(), Error> { let key = peer_addr.as_key(); self.db.delete(Some(PEER_PREFIX), key.as_bytes())?; + self.db.commit()?; Ok(()) }