From 8c0214f461965120349e6a5e85a288ede0141eb0 Mon Sep 17 00:00:00 2001 From: "rearden-grok[bot]" <317016512+rearden-grok[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 06:45:08 -0700 Subject: [PATCH] net: keep unreachable I2P out of addrman Core stores an I2P addrv2 row only when -i2psam made the network reachable. Without that, p2p_addrv2_relay expects getnodeaddresses network=i2p to be empty even though the row is still relayed. --- CHANGELOG.md | 4 +++ crates/rbitcoin-net/src/peers.rs | 54 ++++++++++++++++++++++++++++++++ crates/rbitcoin-node/src/run.rs | 3 ++ 3 files changed, 61 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 746556ece..885e7198c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ before 1.0). ### Fixed +- **Unreachable I2P stays out of addrman:** without `--i2p-sam`, an addrv2 + I2P row is relayed and not stored. `getnodeaddresses` `network=i2p` is + empty. Core `p2p_addrv2_relay`. + - **Orphan parent GETDATA flush cap is 100:** Core `MAX_PEER_TX_REQUEST_IN_FLIGHT`. A 25-tx ancestor package's missing parents fit one GetData (was 16). diff --git a/crates/rbitcoin-net/src/peers.rs b/crates/rbitcoin-net/src/peers.rs index 11564850b..4a1aaa140 100644 --- a/crates/rbitcoin-net/src/peers.rs +++ b/crates/rbitcoin-net/src/peers.rs @@ -1160,6 +1160,8 @@ pub struct PeerHub { /// Clearnet P2P bind (not onion-only loopback). Needed to gossip `--external-ip`. clearnet_listen: AtomicBool, cjdns_reachable: AtomicBool, + /// Core `-i2psam`: I2P rows may enter addrman. Off, they are still relayed. + i2p_reachable: AtomicBool, asmap: Mutex>>, /// Tip-mode mempool for Core `EraseForPeer` on disconnect. mempool: Mutex>>, @@ -1248,6 +1250,7 @@ impl PeerHub { discover: AtomicBool::new(true), clearnet_listen: AtomicBool::new(true), cjdns_reachable: AtomicBool::new(false), + i2p_reachable: AtomicBool::new(false), asmap: Mutex::new(None), mempool: Mutex::new(None), net_perms: Mutex::new(crate::net_permissions::NetPermTable::default()), @@ -1341,6 +1344,11 @@ impl PeerHub { self.cjdns_reachable.store(on, Ordering::Relaxed); } + /// Core sets `NET_I2P` reachable only when `-i2psam` is configured. + pub fn set_i2p_reachable(&self, on: bool) { + self.i2p_reachable.store(on, Ordering::Relaxed); + } + pub fn set_listen_port(&self, port: u16) { self.listen_port.store(port, Ordering::Relaxed); } @@ -1422,8 +1430,14 @@ impl PeerHub { let Some(am) = g.as_ref() else { return; }; + let i2p_ok = self.i2p_reachable.load(Ordering::Relaxed); let mut book = am.lock().unwrap_or_else(|e| e.into_inner()); for a in list { + // No `-i2psam`: relay the addrv2 row, do not store it. Core + // `p2p_addrv2_relay` expects `getnodeaddresses network=i2p` empty. + if matches!(a.addr, bitcoin::p2p::address::AddrV2::I2p(_)) && !i2p_ok { + continue; + } if let Some(addr) = crate::NetAddr::from_addrv2(a) { book.add_learned_addr(addr, crate::seeds::MAX_ADDR_MAN); } @@ -3335,6 +3349,44 @@ mod tests { assert_eq!(hint.port(), 8333); } + #[test] + fn learn_addrv2_drops_i2p_until_reachable() { + use bitcoin::p2p::address::{AddrV2, AddrV2Message}; + use std::net::Ipv4Addr; + + let hub = PeerHub::new(); + let am = Arc::new(Mutex::new(crate::seeds::AddrMan::new())); + hub.set_addrman(am.clone()); + let dest = [0x33u8; 32]; + let v4 = Ipv4Addr::new(123, 123, 123, 1); + hub.learn_addrv2(&[ + AddrV2Message { + time: 1, + services: ServiceFlags::NETWORK, + addr: AddrV2::Ipv4(v4), + port: 8333, + }, + AddrV2Message { + time: 2, + services: ServiceFlags::NETWORK, + addr: AddrV2::I2p(dest), + port: 0, + }, + ]); + let book = am.lock().unwrap_or_else(|e| e.into_inner()); + let addrs: Vec<_> = book.entries().iter().map(|e| e.addr).collect(); + assert!( + addrs.iter().any(|a| matches!(a, crate::NetAddr::Ip(_))), + "{addrs:?}" + ); + assert!( + !addrs + .iter() + .any(|a| matches!(a, crate::NetAddr::I2p { .. })), + "unreachable I2P must not enter addrman, got {addrs:?}" + ); + } + #[test] fn learn_addrv2_keeps_i2p() { use bitcoin::p2p::address::{AddrV2, AddrV2Message}; @@ -3342,6 +3394,7 @@ mod tests { let hub = PeerHub::new(); let am = Arc::new(Mutex::new(crate::seeds::AddrMan::new())); hub.set_addrman(am.clone()); + hub.set_i2p_reachable(true); let dest = [0x11u8; 32]; hub.learn_addrv2(&[AddrV2Message { time: 1, @@ -3365,6 +3418,7 @@ mod tests { let hub = PeerHub::new(); let am = Arc::new(Mutex::new(crate::seeds::AddrMan::new())); hub.set_addrman(am.clone()); + hub.set_i2p_reachable(true); let dest = [0x22u8; 32]; hub.learn_addrv2(&[AddrV2Message { time: 1, diff --git a/crates/rbitcoin-node/src/run.rs b/crates/rbitcoin-node/src/run.rs index b6ba607f1..060b55d8f 100644 --- a/crates/rbitcoin-node/src/run.rs +++ b/crates/rbitcoin-node/src/run.rs @@ -399,6 +399,9 @@ pub async fn run_p2p(config: NodeConfig) -> Result<(), NodeError> { node.peers .set_p2p_onion(format!("{}.onion", hs.service_id), virt); } + if config.listen.i2p_sam.is_some() { + node.peers.set_i2p_reachable(true); + } let mut i2p_sam = if let Some(addr) = config.listen.i2p_sam { let s = if config.listen.i2p_accept_incoming { let dest = config.datadir.path().join("i2p").join("p2p.priv");