use core::net::IpAddr;
use core::net::Ipv4Addr;
use core::net::Ipv6Addr;
use core::net::SocketAddr;
use core::str::FromStr;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs::read_to_string;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use bitcoin::p2p::address::AddrV2;
use bitcoin::p2p::address::AddrV2Message;
use bitcoin::p2p::ServiceFlags;
use bitcoin::Network;
use floresta_chain::DnsSeed;
use floresta_common::service_flags;
use rand::seq::IteratorRandom;
use serde::Deserialize;
use serde::Serialize;
use tracing::debug;
use tracing::error;
use tracing::info;
use tracing::warn;
const RETRY_TIME: u64 = 10 * 60; const MIN_ADDRESSES: usize = 15;
const MIN_ADDRESSES_CBF: usize = 5;
const MIN_ADDRESSES_UTREEXO: usize = 2;
const ASSUME_STALE: u64 = 24 * 60 * 60; const MAX_ADDRESSES: usize = 50_000;
pub const SUPPORTED_NETWORKS: &[ReachableNetworks] =
&[ReachableNetworks::IPv4, ReachableNetworks::IPv6];
type AddressToSend = Vec<(AddrV2, u64, ServiceFlags, u16)>;
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
pub enum AddressState {
NeverTried,
Tried(u64),
Banned(u64),
Connected,
Failed(u64),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ReachableNetworks {
IPv4,
IPv6,
TorV3,
I2P,
Cjdns,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LocalAddress {
address: AddrV2,
last_connected: u64,
state: AddressState,
services: ServiceFlags,
port: u16,
pub id: usize,
}
impl From<AddrV2> for LocalAddress {
fn from(value: AddrV2) -> Self {
LocalAddress {
address: value,
last_connected: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
state: AddressState::NeverTried,
services: ServiceFlags::NONE,
port: 8333,
id: rand::random::<usize>(),
}
}
}
impl From<AddrV2Message> for LocalAddress {
fn from(value: AddrV2Message) -> Self {
LocalAddress {
address: value.addr,
last_connected: value.time.into(),
state: AddressState::NeverTried,
services: value.services,
port: value.port,
id: rand::random::<usize>(),
}
}
}
impl FromStr for LocalAddress {
fn from_str(s: &str) -> Result<Self, Self::Err> {
LocalAddress::try_from(s)
}
type Err = core::net::AddrParseError;
}
impl TryFrom<&str> for LocalAddress {
fn try_from(value: &str) -> Result<Self, Self::Error> {
let address = value.parse::<SocketAddr>()?;
let ip = match address {
SocketAddr::V4(ipv4) => AddrV2::Ipv4(*ipv4.ip()),
SocketAddr::V6(ipv6) => AddrV2::Ipv6(*ipv6.ip()),
};
Ok(LocalAddress::new(
ip,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
super::address_man::AddressState::NeverTried,
ServiceFlags::NONE,
address.port(),
rand::random::<usize>(),
))
}
type Error = core::net::AddrParseError;
}
impl LocalAddress {
pub fn new(
address: AddrV2,
last_connected: u64,
state: AddressState,
services: ServiceFlags,
port: u16,
id: usize,
) -> LocalAddress {
LocalAddress {
address,
last_connected,
state,
services,
port,
id,
}
}
pub fn get_addrv2(&self) -> AddrV2 {
self.address.clone()
}
pub fn get_socket_address(&self) -> SocketAddr {
let ip = self.get_net_address();
let port = self.get_port();
SocketAddr::new(ip, port)
}
pub fn get_port(&self) -> u16 {
self.port
}
pub fn set_port(&mut self, port: u16) {
self.port = port;
}
pub fn get_services(&self) -> ServiceFlags {
self.services
}
pub fn set_services(&mut self, services: ServiceFlags) {
self.services = services;
}
pub fn get_net_address(&self) -> IpAddr {
match self.address {
AddrV2::Ipv4(ipv4) => IpAddr::V4(ipv4),
AddrV2::Ipv6(ipv6) => IpAddr::V6(ipv6),
_ => IpAddr::V4(Ipv4Addr::LOCALHOST),
}
}
const fn is_routable(&self) -> bool {
match self.address {
AddrV2::Ipv4(ipv4) => Self::is_routable_ipv4(&ipv4),
AddrV2::Ipv6(ipv6) => Self::is_routable_ipv6(&ipv6),
AddrV2::Cjdns(address) => {
let octets = address.octets();
if octets[0] == 0xFC {
return true;
}
false
}
_ => true,
}
}
const fn is_routable_ipv4(ip: &Ipv4Addr) -> bool {
let octets = ip.octets();
if octets[0] == 0 {
return false;
}
if ip.is_loopback() || ip.is_broadcast() || ip.is_private() {
return false;
}
if octets[0] == 198 && (octets[1] == 18 || octets[1] == 19) {
return false;
}
if ip.is_link_local() {
return false;
}
if octets[0] == 100 && (octets[1] >= 64 && octets[1] <= 127) {
return false;
}
if ip.is_documentation() {
return false;
}
true
}
#[rustfmt::skip]
const fn is_routable_ipv6(ip: &Ipv6Addr) -> bool {
let octets = ip.octets();
if ip.is_unspecified() || ip.is_loopback() || (ip.segments()[0] & 0xfe00) == 0xfc00 {
return false;
}
if octets[0] == 0x20 && octets[1] == 0x01 && octets[2] == 0x00 && (octets[3] & 0xF0) == 0x10 {
return false;
}
if octets[0] == 0xFE && (octets[1] & 0xC0) == 0x80 {
return false;
}
if octets[0] == 0x20 && octets[1] == 0x01 && octets[2] == 0x00 && (octets[3] & 0xf0) == 0x20 {
return false;
}
true
}
pub fn is_good_address(&self) -> bool {
if !self.is_routable() {
return false;
}
matches!(self.state, AddressState::Connected)
|| matches!(self.state, AddressState::Tried(_))
}
}
#[derive(Clone)]
pub struct AddressMan {
addresses: HashMap<usize, LocalAddress>,
good_addresses: Vec<usize>,
good_peers_by_service: HashMap<ServiceFlags, Vec<usize>>,
peers_by_service: HashMap<ServiceFlags, Vec<usize>>,
max_size: usize,
reachable_networks: HashSet<ReachableNetworks>,
}
impl AddressMan {
pub fn new(max_size: Option<usize>, reachable_networks: &[ReachableNetworks]) -> Self {
let reachable_networks: HashSet<ReachableNetworks> =
reachable_networks.iter().cloned().collect();
AddressMan {
addresses: HashMap::new(),
good_addresses: Vec::new(),
good_peers_by_service: HashMap::new(),
peers_by_service: HashMap::new(),
max_size: max_size.unwrap_or(MAX_ADDRESSES),
reachable_networks,
}
}
fn time_since_unix() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
pub fn push_addresses(&mut self, addresses: &[LocalAddress]) {
for address in addresses {
let id = address.id;
if !address.services.has(ServiceFlags::WITNESS)
|| !address.services.has(ServiceFlags::NETWORK_LIMITED)
{
continue;
}
if !self.is_net_reachable(address) {
continue;
}
if !address.is_routable() {
continue;
}
if self
.addresses
.values()
.any(|x| x.address == address.address)
{
continue;
}
if let std::collections::hash_map::Entry::Vacant(e) = self.addresses.entry(id) {
e.insert(address.clone());
if address.is_good_address() {
self.good_addresses.push(id);
}
self.push_if_has_service(address, service_flags::UTREEXO.into());
self.push_if_has_service(address, ServiceFlags::NONE); self.push_if_has_service(address, ServiceFlags::COMPACT_FILTERS);
}
}
self.prune_addresses();
}
fn is_net_reachable(&self, address: &LocalAddress) -> bool {
match address.address {
AddrV2::Ipv4(_) => self.reachable_networks.contains(&ReachableNetworks::IPv4),
AddrV2::Ipv6(_) => self.reachable_networks.contains(&ReachableNetworks::IPv6),
AddrV2::TorV3(_) => self.reachable_networks.contains(&ReachableNetworks::TorV3),
AddrV2::I2p(_) => self.reachable_networks.contains(&ReachableNetworks::I2P),
AddrV2::Cjdns(_) => self.reachable_networks.contains(&ReachableNetworks::Cjdns),
_ => false,
}
}
fn prune_addresses(&mut self) {
let excess = self.addresses.len().saturating_sub(self.max_size);
if excess == 0 {
return;
}
let mut oldest_ids: Vec<_> = self
.addresses
.iter()
.map(|(&id, addr)| (id, addr.last_connected))
.collect();
oldest_ids.sort_by_key(|&(_, last_connected)| last_connected);
for (oldest_id, _) in oldest_ids.into_iter().take(excess) {
self.addresses.remove(&oldest_id);
self.good_addresses.retain(|&x| x != oldest_id);
for peers in self.good_peers_by_service.values_mut() {
peers.retain(|&x| x != oldest_id);
}
for peers in self.peers_by_service.values_mut() {
peers.retain(|&x| x != oldest_id);
}
}
}
fn get_addresses_by_service(&self, service: ServiceFlags) -> Vec<LocalAddress> {
self.good_peers_by_service
.get(&service)
.map(|peer_ids| {
peer_ids
.iter()
.filter_map(|id| self.addresses.get(id).cloned())
.collect()
})
.unwrap_or_default()
}
#[rustfmt::skip]
pub fn enough_addresses(&self) -> bool {
if self.good_addresses.len() < MIN_ADDRESSES {
return false;
}
if self.get_addresses_by_service(ServiceFlags::COMPACT_FILTERS).len() < MIN_ADDRESSES_CBF {
return false;
}
if self.get_addresses_by_service(service_flags::UTREEXO.into()).len() < MIN_ADDRESSES_UTREEXO {
return false;
}
true
}
fn push_if_has_service(&mut self, address: &LocalAddress, service: ServiceFlags) {
if !address.services.has(service) {
return;
}
let addresses = self.peers_by_service.entry(service).or_default();
if !addresses.contains(&address.id) {
self.peers_by_service
.entry(service)
.or_default()
.push(address.id);
}
let addresses = self.good_peers_by_service.entry(service).or_default();
if !addresses.contains(&address.id) && address.is_good_address() {
self.good_peers_by_service
.entry(service)
.or_default()
.push(address.id);
}
}
pub fn get_addresses_to_send(&self) -> AddressToSend {
let addresses = self
.good_addresses
.iter()
.filter_map(|id| {
let address = self.addresses.get(id)?;
Some((
address.address.clone(),
address.last_connected,
address.services,
address.port,
))
})
.collect();
addresses
}
fn do_lookup(host: &str, default_port: u16, socks5: Option<SocketAddr>) -> Vec<LocalAddress> {
let ips = match socks5 {
Some(proxy) => {
debug!("Performing DNS lookup for host: {host}, using SOCKS5 proxy: {proxy}");
dns_proxy::lookup_host_via_proxy(host, proxy).unwrap_or_else(|e| {
error!("DNS lookup via SOCKS5 proxy failed: {e}");
Vec::new()
})
}
None => {
debug!("Performing DNS lookup for host: {host}, using the system resolver");
dns_lookup::lookup_host(host).unwrap_or_else(|e| {
error!("DNS lookup failed: {e}");
Vec::new()
})
}
};
if ips.is_empty() {
warn!("No peer addresses read from DNS host: {host}");
} else {
info!("Fetched {} peer addresses from DNS host: {host}", ips.len());
}
let mut addresses = Vec::new();
for ip in ips {
if let Ok(ip) = LocalAddress::try_from(format!("{ip}:{default_port}").as_str()) {
addresses.push(ip);
}
}
addresses
}
pub fn get_seeds_from_dns(
seed: &DnsSeed,
default_port: u16,
socks5: Option<SocketAddr>,
) -> Result<Vec<LocalAddress>, std::io::Error> {
let mut addresses = Vec::new();
let now = Self::time_since_unix();
if seed.filters.has(service_flags::UTREEXO.into()) {
let host = format!("x1000.{}", seed.seed);
let _addresses = Self::do_lookup(&host, default_port, socks5);
let _addresses = _addresses.into_iter().map(|mut x| {
x.services = ServiceFlags::NETWORK_LIMITED
| service_flags::UTREEXO.into()
| ServiceFlags::WITNESS;
x.state = AddressState::Tried(now);
x
});
addresses.extend(_addresses);
}
if seed.filters.has(ServiceFlags::COMPACT_FILTERS) {
let host = format!("x49.{}", seed.seed);
let _addresses = Self::do_lookup(&host, default_port, socks5);
let _addresses = _addresses.into_iter().map(|mut x| {
x.services = ServiceFlags::COMPACT_FILTERS
| ServiceFlags::NETWORK_LIMITED
| ServiceFlags::WITNESS;
x.state = AddressState::Tried(now);
x
});
addresses.extend(_addresses);
}
if seed.filters.has(ServiceFlags::WITNESS) {
let host = format!("x9.{}", seed.seed);
let _addresses = Self::do_lookup(&host, default_port, socks5);
let _addresses = _addresses.into_iter().map(|mut x| {
x.services = ServiceFlags::NETWORK_LIMITED | ServiceFlags::WITNESS;
x.state = AddressState::Tried(now);
x
});
addresses.extend(_addresses);
}
if seed.filters == ServiceFlags::NONE {
let _addresses = Self::do_lookup(seed.seed, default_port, socks5);
let _addresses = _addresses.into_iter().map(|mut x| {
x.services = ServiceFlags::NETWORK_LIMITED | ServiceFlags::WITNESS;
x.state = AddressState::Tried(now);
x
});
addresses.extend(_addresses);
}
Ok(addresses)
}
pub fn get_address_to_connect(
&mut self,
required_service: ServiceFlags,
feeler: bool,
) -> Option<(usize, LocalAddress)> {
if self.addresses.is_empty() {
return None;
}
if feeler {
let idx = rand::random::<usize>() % self.addresses.len();
let peer = self.addresses.keys().nth(idx)?;
let address = self.addresses.get(peer)?.to_owned();
if matches!(address.state, AddressState::Banned(_))
| matches!(address.state, AddressState::Connected)
{
return None;
}
return Some((*peer, address));
};
for _ in 0..10 {
let (id, peer) = self
.get_address_by_service(required_service)
.or_else(|| self.get_random_address(required_service))?;
match peer.state {
AddressState::NeverTried | AddressState::Tried(_) => {
return Some((id, peer));
}
AddressState::Connected => {
continue;
}
AddressState::Failed(when) => {
let now = Self::time_since_unix();
if when + RETRY_TIME < now {
return Some((id, peer));
}
if let Some(peers) = self.good_peers_by_service.get_mut(&required_service) {
peers.retain(|&x| x != id)
}
self.good_addresses.retain(|&x| x != id);
}
AddressState::Banned(_) => {}
}
}
None
}
pub fn dump_peers(&self, datadir: &str) -> std::io::Result<()> {
let peers: Vec<_> = self
.addresses
.values()
.cloned()
.map(Into::<DiskLocalAddress>::into)
.collect::<Vec<_>>();
let peers = serde_json::to_string(&peers);
if let Ok(peers) = peers {
std::fs::write(datadir.to_owned() + "/peers.json", peers)?;
}
Ok(())
}
pub fn dump_utreexo_peers(&self, datadir: &str, peers_id: &[usize]) -> std::io::Result<()> {
let addresses: Vec<DiskLocalAddress> = peers_id
.iter()
.filter_map(|id| Some(self.addresses.get(id)?.to_owned().into()))
.collect();
let addresses: Result<String, serde_json::Error> = serde_json::to_string(&addresses);
if let Ok(addresses) = addresses {
std::fs::write(datadir.to_owned() + "/anchors.json", addresses)?;
}
Ok(())
}
fn get_address_by_service(&self, service: ServiceFlags) -> Option<(usize, LocalAddress)> {
let candidates = self.good_peers_by_service.get(&service)?;
candidates
.iter()
.filter_map(|id| {
let addr = self.addresses.get(id)?;
(addr.state != AddressState::Connected).then_some((id, addr))
})
.choose(&mut rand::thread_rng())
.map(|(id, addr)| (*id, addr.to_owned()))
}
pub fn start_addr_man(&mut self, datadir: String) -> Vec<LocalAddress> {
let persisted_peers = read_to_string(format!("{datadir}/peers.json"))
.map(|seeds| serde_json::from_str::<Vec<DiskLocalAddress>>(&seeds));
if let Ok(Ok(peers)) = persisted_peers {
let peers = peers
.into_iter()
.map(Into::<LocalAddress>::into)
.collect::<Vec<_>>();
self.push_addresses(&peers);
}
let anchors = read_to_string(format!("{datadir}/anchors.json")).and_then(|anchors| {
let anchors = serde_json::from_str::<Vec<DiskLocalAddress>>(&anchors)?;
Ok(anchors
.into_iter()
.map(Into::<LocalAddress>::into)
.collect::<Vec<_>>())
});
if anchors.is_err() {
warn!("Failed to init Utreexo peers: anchors.json does not exist yet, or is invalid");
}
anchors.unwrap_or_default()
}
pub fn rearrange_buckets(&mut self) {
let now = Self::time_since_unix();
for (_, address) in self.addresses.iter_mut() {
match address.state {
AddressState::Banned(ban_time) => {
if ban_time < now {
address.state = AddressState::NeverTried;
}
}
AddressState::Tried(tried_time) => {
if tried_time + ASSUME_STALE < now {
address.state = AddressState::NeverTried;
}
}
AddressState::Failed(failed_time) => {
if failed_time + ASSUME_STALE < now {
address.state = AddressState::NeverTried;
}
}
AddressState::Connected | AddressState::NeverTried => {}
}
}
}
fn try_with_service(&self, service: ServiceFlags) -> Option<(usize, LocalAddress)> {
if let Some(peers) = self.peers_by_service.get(&service) {
let peers = peers
.iter()
.filter(|&x| {
if let Some(address) = self.addresses.get(x) {
if let AddressState::Failed(when) = address.state {
let now = Self::time_since_unix();
if (when + RETRY_TIME) < now {
return true;
}
}
return matches!(address.state, AddressState::Tried(_))
|| matches!(address.state, AddressState::NeverTried);
}
false
})
.collect::<Vec<_>>();
if peers.is_empty() {
return None;
}
let idx = rand::random::<usize>() % peers.len();
let utreexo_peer = peers.get(idx)?;
return Some((**utreexo_peer, self.addresses.get(utreexo_peer)?.to_owned()));
}
None
}
fn get_random_address(&self, service: ServiceFlags) -> Option<(usize, LocalAddress)> {
if self.addresses.is_empty() {
return None;
}
if let Some(address) = self.try_with_service(service) {
return Some(address);
}
let idx = rand::random::<usize>() % self.addresses.len();
let peer = self.addresses.keys().nth(idx)?;
Some((*peer, self.addresses.get(peer)?.to_owned()))
}
pub fn update_set_state(&mut self, idx: usize, state: AddressState) -> &mut Self {
if let Some(address) = self.addresses.get_mut(&idx) {
address.state = state;
}
match state {
AddressState::Banned(_) => {
self.good_addresses.retain(|&x| x != idx);
}
AddressState::Tried(_) => {
if !self.good_addresses.contains(&idx) {
self.good_addresses.push(idx);
}
if let Some(address) = self.addresses.get(&idx).cloned() {
self.push_if_has_service(&address, service_flags::UTREEXO.into());
self.push_if_has_service(&address, service_flags::UTREEXO_ARCHIVE.into());
self.push_if_has_service(&address, ServiceFlags::NONE); self.push_if_has_service(&address, ServiceFlags::COMPACT_FILTERS);
}
}
AddressState::NeverTried => {
self.good_addresses.retain(|&x| x != idx);
}
AddressState::Connected => {
self.addresses.entry(idx).and_modify(|addr| {
addr.last_connected = Self::time_since_unix();
});
if !self.good_addresses.contains(&idx) {
self.good_addresses.push(idx);
}
if let Some(address) = self.addresses.get(&idx).cloned() {
self.push_if_has_service(&address, service_flags::UTREEXO.into());
self.push_if_has_service(&address, ServiceFlags::NONE); self.push_if_has_service(&address, ServiceFlags::COMPACT_FILTERS);
}
}
AddressState::Failed(_) => {
self.good_addresses.retain(|&x| x != idx);
for peers in self.good_peers_by_service.values_mut() {
peers.retain(|&x| x != idx);
}
}
}
self
}
fn add_peer_to_service(&mut self, idx: usize, service: ServiceFlags) {
if let Some(peers) = self.peers_by_service.get_mut(&service) {
if peers.contains(&idx) {
return;
}
peers.push(idx);
} else {
self.peers_by_service.insert(service, vec![idx]);
}
}
fn remove_peer_from_service(&mut self, idx: usize, service: ServiceFlags) {
if let Some(peers) = self.peers_by_service.get_mut(&service) {
peers.retain(|&x| x != idx);
}
}
fn update_peer_for_service(&mut self, id: usize, service: ServiceFlags) {
let Some(peer) = self.addresses.get(&id) else {
return;
};
match peer.services.has(service) {
true => self.add_peer_to_service(id, service),
false => self.remove_peer_from_service(id, service),
}
}
fn update_peer_services_buckets(&mut self, idx: usize) {
self.update_peer_for_service(idx, service_flags::UTREEXO.into());
self.update_peer_for_service(idx, ServiceFlags::COMPACT_FILTERS);
}
pub fn update_set_service_flag(&mut self, idx: usize, flags: ServiceFlags) -> &mut Self {
if !flags.has(ServiceFlags::NETWORK_LIMITED) || !flags.has(ServiceFlags::WITNESS) {
self.addresses.remove(&idx);
for peers in self.peers_by_service.values_mut() {
peers.retain(|&x| x != idx);
}
self.good_addresses.retain(|&x| x != idx);
self.good_peers_by_service
.values_mut()
.for_each(|peers| peers.retain(|&x| x != idx));
return self;
}
if let Some(address) = self.addresses.get_mut(&idx) {
address.services = flags;
}
self.update_peer_services_buckets(idx);
self
}
const fn get_net_seeds(network: Network) -> &'static str {
match network {
Network::Bitcoin => include_str!("../../seeds/mainnet_seeds.json"),
Network::Signet => include_str!("../../seeds/signet_seeds.json"),
Network::Testnet => include_str!("../../seeds/testnet_seeds.json"),
Network::Testnet4 => include_str!("../../seeds/testnet4_seeds.json"),
Network::Regtest => include_str!("../../seeds/regtest_seeds.json"),
}
}
pub(crate) fn add_fixed_addresses(&mut self, network: Network) {
let addresses = Self::get_net_seeds(network);
let peers: Vec<DiskLocalAddress> =
serde_json::from_str(addresses).expect("BUG: fixed peers are invalid");
let peers = peers
.into_iter()
.map(Into::<LocalAddress>::into)
.collect::<Vec<_>>();
self.push_addresses(&peers);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskLocalAddress {
address: Address,
last_connected: u64,
state: AddressState,
services: u64,
port: u16,
id: Option<usize>,
}
impl From<LocalAddress> for DiskLocalAddress {
fn from(value: LocalAddress) -> Self {
let address = match value.address {
AddrV2::Ipv4(ip) => Address::V4(ip),
AddrV2::Ipv6(ip) => Address::V6(ip),
AddrV2::Cjdns(ip) => Address::Cjdns(ip),
AddrV2::I2p(ip) => Address::I2p(ip),
AddrV2::TorV2(ip) => Address::OnionV2(ip),
AddrV2::TorV3(ip) => Address::OnionV3(ip),
AddrV2::Unknown(_, _) => Address::V4(Ipv4Addr::LOCALHOST),
};
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
DiskLocalAddress {
address,
last_connected: value.last_connected,
state: if value.state == AddressState::Connected {
AddressState::Tried(time)
} else {
value.state
},
services: value.services.to_u64(),
port: value.port,
id: Some(value.id),
}
}
}
impl From<DiskLocalAddress> for LocalAddress {
fn from(value: DiskLocalAddress) -> Self {
let address = match value.address {
Address::V4(ip) => AddrV2::Ipv4(ip),
Address::V6(ip) => AddrV2::Ipv6(ip),
Address::Cjdns(ip) => AddrV2::Cjdns(ip),
Address::I2p(ip) => AddrV2::I2p(ip),
Address::OnionV2(ip) => AddrV2::TorV2(ip),
Address::OnionV3(ip) => AddrV2::TorV3(ip),
};
let services = ServiceFlags::from(value.services);
LocalAddress {
address,
last_connected: value.last_connected,
state: value.state,
services,
port: value.port,
id: value.id.unwrap_or_else(rand::random::<usize>),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Address {
V4(Ipv4Addr),
V6(Ipv6Addr),
OnionV2([u8; 10]),
OnionV3([u8; 32]),
Cjdns(Ipv6Addr),
I2p([u8; 32]),
}
impl From<Address> for AddrV2 {
fn from(value: Address) -> Self {
match value {
Address::V4(addr) => AddrV2::Ipv4(addr),
Address::V6(addr) => AddrV2::Ipv6(addr),
Address::I2p(addr) => AddrV2::I2p(addr),
Address::Cjdns(addr) => AddrV2::Cjdns(addr),
Address::OnionV2(addr) => AddrV2::TorV2(addr),
Address::OnionV3(addr) => AddrV2::TorV3(addr),
}
}
}
pub mod dns_proxy {
use core::net::IpAddr;
use core::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use rustls::crypto;
use serde::Deserialize;
use ureq::tls::TlsConfig;
use ureq::tls::TlsProvider;
use ureq::Agent;
use ureq::Proxy;
#[derive(Deserialize)]
struct DnsResponse {
#[serde(rename = "Answer")]
answers: Option<Vec<AnswerEntry>>,
}
#[derive(Deserialize)]
struct AnswerEntry {
data: String,
#[serde(rename = "type")]
record_type: u8,
}
pub fn lookup_host_via_proxy(
host: &str,
proxy_addr: SocketAddr,
) -> Result<Vec<IpAddr>, ureq::Error> {
let proxy = Proxy::new(&format!("socks5://{proxy_addr}"))?;
let crypto = Arc::new(crypto::aws_lc_rs::default_provider());
let tls_config = TlsConfig::builder()
.provider(TlsProvider::Rustls)
.unversioned_rustls_crypto_provider(crypto)
.build();
let agent: Agent = Agent::config_builder()
.tls_config(tls_config)
.timeout_global(Some(Duration::from_secs(30)))
.proxy(Some(proxy))
.build()
.into();
let mut all_ips = Vec::new();
for record_type in [1u8, 28u8] {
let mut ips = query(&agent, host, record_type)?;
all_ips.append(&mut ips);
}
Ok(all_ips)
}
fn query(agent: &Agent, host: &str, record_type: u8) -> Result<Vec<IpAddr>, ureq::Error> {
let url = format!("https://dns.google/resolve?name={host}&type={record_type}");
let mut response = agent.get(&url).call()?;
let dns_response: DnsResponse = response.body_mut().read_json()?;
let answers = dns_response.answers.unwrap_or_default();
let mut result = Vec::new();
for entry in answers.into_iter().filter(|e| e.record_type == record_type) {
if let Ok(ip) = entry.data.parse() {
result.push(ip);
}
}
Ok(result)
}
}
#[cfg(test)]
mod test {
use std::fs::File;
use std::io::Read;
use std::io::{self};
use bitcoin::p2p::address::AddrV2;
use bitcoin::p2p::ServiceFlags;
use bitcoin::Network;
use floresta_chain::get_chain_dns_seeds;
use floresta_common::assert_ok;
use floresta_common::service_flags;
use rand::Rng;
use super::AddressState;
use super::LocalAddress;
use crate::address_man::AddressMan;
use crate::address_man::DiskLocalAddress;
use crate::address_man::ReachableNetworks;
use crate::address_man::SUPPORTED_NETWORKS;
fn load_addresses_from_json(file_path: &str) -> io::Result<Vec<LocalAddress>> {
let mut contents = String::new();
File::open(file_path)?.read_to_string(&mut contents)?;
let seeds: Vec<DiskLocalAddress> =
serde_json::from_str(&contents).expect("JSON not well-formatted");
let mut addresses = Vec::new();
let mut rng = rand::thread_rng();
for seed in seeds {
let state = match seed.state {
AddressState::Tried(time) => AddressState::Tried(time),
_ => continue,
};
let local_address = LocalAddress {
address: seed.address.into(),
last_connected: seed.last_connected,
state,
services: ServiceFlags::from(seed.services),
port: seed.port,
id: rng.gen(),
};
addresses.push(local_address);
}
Ok(addresses)
}
#[test]
fn test_local_addr_from_str() {
let ips = [
"127.146.182.45",
"2.212.31.248",
"6.108.160.10",
"151.43.223.99",
"216.20.167.190",
"188.33.163.249",
"227.237.60.84",
"8.104.121.145",
"100.119.250.124",
];
for addr_str in ips {
let local_address = LocalAddress::try_from(format!("{addr_str}:8333").as_str())
.unwrap_or_else(|_| panic!("failed to parse {addr_str}"));
assert_eq!(
local_address.address,
AddrV2::Ipv4(addr_str.parse().unwrap())
);
assert_eq!(local_address.port, 8333);
}
let ips = [
"67db:3727:f145:5c59:718f:d3b9:6e56:d937",
"7813:70c7:ea5d:f78a:7920:33d8:1da0:f9d7",
"4a08:75e4:893f:d5a1:e2e2:3c99:8d20:22cf",
"8da0:6b59:1494:bc7f:b217:51eb:c5fb:29c6",
"cb1a:5104:57a9:0616:f6e0:191f:9224:4f35",
"259a:ddc7:44a2:b5ec:f1ff:6024:50e8:928d",
"46eb:cab1:bd48:c461:1775:c64e:c11b:3e77",
"142b:a452:dff7:a41c:6cc6:317e:cc94:bb10",
"0f8d:6d08:de58:017a:cd92:c868:023a:86e6",
"8a80:5cfd:ccac:3e63:a243:d89f:d5e1:8e4c",
];
for addr_str in ips {
let local_address = LocalAddress::try_from(format!("[{addr_str}]:8333").as_str())
.unwrap_or_else(|_| panic!("failed to parse {addr_str}"));
assert_eq!(
local_address.address,
AddrV2::Ipv6(addr_str.parse().unwrap())
);
assert_eq!(local_address.port, 8333);
}
}
#[test]
fn test_adding_fixed_peer() {
let signet_addresses = load_addresses_from_json("./seeds/signet_seeds.json").unwrap();
let mut addr_man =
AddressMan::new(None, &[ReachableNetworks::IPv4, ReachableNetworks::IPv6]);
addr_man.add_fixed_addresses(Network::Signet);
assert_eq!(addr_man.good_addresses.len(), signet_addresses.len());
let utreexo_addresses = signet_addresses
.iter()
.filter(|address| address.services.has(service_flags::UTREEXO.into()))
.collect::<Vec<_>>();
assert_eq!(
addr_man
.good_peers_by_service
.get(&service_flags::UTREEXO.into())
.unwrap()
.len(),
utreexo_addresses.len()
);
assert_eq!(
addr_man
.peers_by_service
.get(&service_flags::UTREEXO.into())
.unwrap()
.len(),
utreexo_addresses.len()
);
}
#[test]
fn test_parse() {
let signet_address = load_addresses_from_json("./seeds/signet_seeds.json").unwrap();
assert!(!signet_address.is_empty());
let random = rand::thread_rng().gen_range(1..=13);
let loc_adr_1 = LocalAddress::from(signet_address[random].address.clone());
assert_eq!(loc_adr_1.address, signet_address[random].address);
}
#[test]
fn test_fixed_peers() {
let _ = load_addresses_from_json("./seeds/signet_seeds.json").unwrap();
let _ = load_addresses_from_json("./seeds/mainnet_seeds.json").unwrap();
let _ = load_addresses_from_json("./seeds/testnet_seeds.json").unwrap();
let _ = load_addresses_from_json("./seeds/regtest_seeds.json").unwrap();
}
#[test]
fn test_address_man() {
let mut address_man =
AddressMan::new(None, &[ReachableNetworks::IPv4, ReachableNetworks::IPv6]);
let signet_address = load_addresses_from_json("./seeds/signet_seeds.json").unwrap();
address_man.push_addresses(&signet_address);
assert!(!address_man.good_addresses.is_empty());
assert!(!address_man.peers_by_service.is_empty());
assert!(!address_man.get_addresses_to_send().is_empty());
assert!(address_man
.get_address_to_connect(ServiceFlags::default(), true)
.is_some());
assert!(address_man
.get_address_to_connect(ServiceFlags::default(), false)
.is_some());
assert!(address_man
.get_address_to_connect(ServiceFlags::NONE, false)
.is_some());
assert!(address_man
.get_address_to_connect(service_flags::UTREEXO.into(), false)
.is_some());
assert!(!AddressMan::get_net_seeds(Network::Signet).is_empty());
assert!(!AddressMan::get_net_seeds(Network::Bitcoin).is_empty());
assert!(!AddressMan::get_net_seeds(Network::Regtest).is_empty());
assert!(!AddressMan::get_net_seeds(Network::Testnet).is_empty());
assert_ok!(AddressMan::get_seeds_from_dns(
&get_chain_dns_seeds(Network::Signet)[0],
8333,
None, ));
address_man.rearrange_buckets();
}
#[test]
fn test_is_routable() {
let addresses = vec![
"10.42.187.23:8333",
"10.0.254.199:8333",
"172.16.88.4:8333",
"172.31.201.77:8333",
"192.168.1.14:8333",
"192.168.203.250:8333",
"0.9.85.249:8333",
"[fd3a:9f2b:4c10:1a2b::1]:8333",
"[fd12:3456:789a:1::dead]:8333",
"[fdff:ab23:9012:beef::42]:8333",
"[fd7c:2e91:aa10:ff01:1234:5678:9abc:def0]:8333",
"[fd00:1111:2222:3333:4444:5555:6666:7777]:8333",
]
.into_iter()
.map(|s| {
LocalAddress::try_from(s).unwrap_or_else(|_| panic!("Failed to parse address: {s}"))
})
.collect::<Vec<_>>();
for address in addresses {
assert!(!address.is_routable(), "{address:?}");
}
let signet_address = load_addresses_from_json("./seeds/signet_seeds.json").unwrap();
for address in signet_address {
assert!(address.is_routable(), "{address:?}");
}
}
fn get_addresses_and_random_times() -> Vec<LocalAddress> {
let signet_address = load_addresses_from_json("./seeds/signet_seeds.json").unwrap();
let now = AddressMan::time_since_unix();
let mut modified_addresses = signet_address.clone();
let addresses = modified_addresses.len();
for (i, item) in modified_addresses.iter_mut().enumerate().take(addresses) {
if i % 3 == 0 {
item.last_connected = now - 5000;
} else if i % 3 == 1 {
item.last_connected = now - 6000;
} else {
item.last_connected = now - 2000;
}
}
modified_addresses
}
#[test]
fn test_rearrange_buckets() {
let mut address_man = AddressMan::new(None, &[]);
let addresses = get_addresses_and_random_times();
address_man.addresses.extend(
addresses
.iter()
.map(|addr| (addr.id, addr.clone()))
.collect::<std::collections::HashMap<usize, LocalAddress>>(),
);
assert_eq!(address_man.addresses.len(), addresses.len());
address_man.rearrange_buckets();
assert!(address_man.addresses.iter().all(|(_, addr)| {
matches!(
addr.state,
AddressState::NeverTried | AddressState::Tried(_)
)
}));
}
#[test]
fn test_is_net_reachable() {
let v4 = "127.146.182.45";
let v6 = "142b:a452:dff7:a41c:6cc6:317e:cc94:bb10";
let addr_v4 = AddrV2::Ipv4(v4.parse().unwrap());
let addr_v6 = AddrV2::Ipv6(v6.parse().unwrap());
let addr_onionv3 = AddrV2::TorV3([
0x89, 0x6c, 0x6a, 0x71, 0x70, 0x6b, 0x67, 0x61, 0x62, 0x67, 0x34, 0x68, 0x72, 0x63,
0x68, 0x62, 0x6f, 0x7a, 0x77, 0x6f, 0x76, 0x66, 0x66, 0x79, 0x6b, 0x37, 0x66, 0x6f,
0x62, 0x70, 0x6f, 0x76,
]);
let addr_i2p = AddrV2::I2p([
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x66, 0x6f,
0x62, 0x70, 0x6f, 0x76,
]);
let address_man =
AddressMan::new(None, &[ReachableNetworks::IPv4, ReachableNetworks::IPv6]);
assert!(address_man.is_net_reachable(&LocalAddress {
address: addr_v4,
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::default(),
port: 8333,
id: 0,
}));
assert!(address_man.is_net_reachable(&LocalAddress {
address: addr_v6,
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::default(),
port: 8333,
id: 0,
}));
assert!(!address_man.is_net_reachable(&LocalAddress {
address: addr_onionv3,
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::default(),
port: 8333,
id: 0,
}));
assert!(!address_man.is_net_reachable(&LocalAddress {
address: addr_i2p,
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::default(),
port: 8333,
id: 0,
}));
}
#[test]
fn test_push_address() {
let mut address_man = AddressMan::new(None, &[ReachableNetworks::IPv4]);
let v4_no_witness = LocalAddress {
address: AddrV2::Ipv4("12.146.182.45".parse().unwrap()),
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::NETWORK | ServiceFlags::NETWORK_LIMITED,
port: 8333,
id: 0,
};
let v4_with_witness = LocalAddress {
address: AddrV2::Ipv4("12.146.182.45".parse().unwrap()),
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::NETWORK | ServiceFlags::NETWORK_LIMITED | ServiceFlags::WITNESS,
port: 8333,
id: 1,
};
let v6_with_witness = LocalAddress {
address: AddrV2::Ipv6("fd3a:9f2b:4c10:1a2b::1".parse().unwrap()),
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::NETWORK_LIMITED | ServiceFlags::NETWORK | ServiceFlags::WITNESS,
port: 8333,
id: 2,
};
let v4_not_routable = LocalAddress {
address: AddrV2::Ipv4("127.0.0.1".parse().unwrap()),
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::NETWORK_LIMITED | ServiceFlags::NETWORK | ServiceFlags::WITNESS,
port: 8333,
id: 3,
};
let v6_not_routable = LocalAddress {
address: AddrV2::Ipv6("::1".parse().unwrap()),
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::NETWORK_LIMITED | ServiceFlags::NETWORK | ServiceFlags::WITNESS,
port: 8333,
id: 4,
};
let onion = LocalAddress {
address: AddrV2::TorV3([
0x89, 0x6c, 0x6a, 0x71, 0x70, 0x6b, 0x67, 0x61, 0x62, 0x67, 0x34, 0x68, 0x72, 0x63,
0x68, 0x62, 0x6f, 0x7a, 0x77, 0x6f, 0x76, 0x66, 0x66, 0x79, 0x6b, 0x37, 0x66, 0x6f,
0x62, 0x70, 0x6f, 0x76,
]),
last_connected: 0,
state: AddressState::NeverTried,
services: ServiceFlags::NETWORK_LIMITED | ServiceFlags::NETWORK | ServiceFlags::WITNESS,
port: 8333,
id: 5,
};
let addresses = vec![
v4_no_witness,
v4_with_witness.clone(),
v6_with_witness,
v4_not_routable,
v6_not_routable,
onion,
];
address_man.push_addresses(&addresses);
assert_eq!(address_man.addresses.len(), 1);
assert_eq!(
*address_man.addresses.values().next().unwrap(),
v4_with_witness
);
}
#[test]
fn test_prune_addresses() {
let mut address_man = AddressMan::new(Some(10), &[]);
let addresses = get_addresses_and_random_times();
address_man.addresses.extend(
addresses
.iter()
.map(|addr| (addr.id, addr.clone()))
.collect::<std::collections::HashMap<usize, LocalAddress>>(),
);
assert_eq!(address_man.addresses.len(), addresses.len(),);
address_man.prune_addresses();
assert_ne!(address_man.addresses.len(), addresses.len());
}
#[test]
fn test_update_address_state() {
let mut address_man = AddressMan::new(None, &[]);
let addresses = get_addresses_and_random_times();
address_man.addresses.extend(
addresses
.iter()
.map(|addr| (addr.id, addr.clone()))
.collect::<std::collections::HashMap<usize, LocalAddress>>(),
);
for addr in addresses {
address_man.update_set_state(addr.id, AddressState::Banned(0));
}
assert!(address_man
.addresses
.values()
.all(|addr| matches!(addr.state, AddressState::Banned(_))));
}
#[test]
fn test_update_service_flags() {
let mut address_man = AddressMan::new(None, &[]);
let addresses = get_addresses_and_random_times();
address_man.addresses.extend(
addresses
.iter()
.map(|addr| (addr.id, addr.clone()))
.collect::<std::collections::HashMap<usize, LocalAddress>>(),
);
for addr in addresses {
address_man.update_set_service_flag(addr.id, service_flags::UTREEXO.into());
}
assert!(address_man
.addresses
.values()
.all(|addr| addr.services.has(service_flags::UTREEXO.into())));
}
#[test]
fn test_add_fixed_addresses() {
let mut address_man = AddressMan::new(None, SUPPORTED_NETWORKS);
address_man.add_fixed_addresses(Network::Signet);
assert!(!address_man.addresses.is_empty());
}
}