use crate::{
config::cache_file_name,
craft_valid_multiaddr, craft_valid_multiaddr_from_str,
error::{Error, Result},
BootstrapAddr, BootstrapCacheConfig, BootstrapCacheStore, ContactsFetcher,
};
use ant_protocol::version::{get_network_id, ALPHANET_ID, MAINNET_ID};
use clap::Args;
use libp2p::Multiaddr;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use url::Url;
pub const ANT_PEERS_ENV: &str = "ANT_PEERS";
#[derive(Args, Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct InitialPeersConfig {
#[clap(long, default_value = "false")]
pub first: bool,
#[clap(
long = "peer",
value_name = "multiaddr",
value_delimiter = ',',
conflicts_with = "first"
)]
pub addrs: Vec<Multiaddr>,
#[clap(long, conflicts_with = "first", value_delimiter = ',')]
pub network_contacts_url: Vec<String>,
#[clap(long, conflicts_with = "network_contacts_url", default_value = "false")]
pub local: bool,
#[clap(long, default_value = "false")]
pub ignore_cache: bool,
#[clap(long)]
pub bootstrap_cache_dir: Option<PathBuf>,
}
impl InitialPeersConfig {
pub async fn get_addrs(
&self,
config: Option<BootstrapCacheConfig>,
count: Option<usize>,
) -> Result<Vec<Multiaddr>> {
Ok(self
.get_bootstrap_addr(config, count)
.await?
.into_iter()
.map(|addr| addr.addr)
.collect())
}
pub async fn get_bootstrap_addr(
&self,
config: Option<BootstrapCacheConfig>,
count: Option<usize>,
) -> Result<Vec<BootstrapAddr>> {
if self.first {
info!("First node in network, no initial bootstrap peers");
return Ok(vec![]);
}
let mut bootstrap_addresses = vec![];
bootstrap_addresses.extend(Self::read_bootstrap_addr_from_env());
if !bootstrap_addresses.is_empty() {
info!(
"Found {} bootstrap addresses from environment variable",
bootstrap_addresses.len()
);
return Ok(bootstrap_addresses);
}
for addr in &self.addrs {
if let Some(addr) = craft_valid_multiaddr(addr, false) {
info!("Adding addr from arguments: {addr}");
bootstrap_addresses.push(BootstrapAddr::new(addr));
} else {
warn!("Invalid multiaddress format from arguments: {addr}");
}
}
if let Some(count) = count {
if bootstrap_addresses.len() >= count {
bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
bootstrap_addresses.truncate(count);
info!(
"Found {} bootstrap addresses. Returning early.",
bootstrap_addresses.len()
);
return Ok(bootstrap_addresses);
}
}
if !self.ignore_cache {
let cfg = if let Some(config) = config {
Some(config)
} else {
BootstrapCacheConfig::default_config(self.local).ok()
};
if let Some(mut cfg) = cfg {
if let Some(file_path) = self.get_bootstrap_cache_path()? {
cfg.cache_file_path = file_path;
}
info!("Loading bootstrap addresses from cache");
if let Ok(data) = BootstrapCacheStore::load_cache_data(&cfg) {
let from_cache = data.peers.into_iter().filter_map(|(_, addrs)| {
addrs
.0
.into_iter()
.min_by_key(|addr| addr.failure_rate() as u64)
});
bootstrap_addresses.extend(from_cache);
if let Some(count) = count {
if bootstrap_addresses.len() >= count {
bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
bootstrap_addresses.truncate(count);
info!(
"Found {} bootstrap addresses. Returning early.",
bootstrap_addresses.len()
);
return Ok(bootstrap_addresses);
}
}
}
}
} else {
info!("Ignoring cache, not loading bootstrap addresses from cache");
}
if !self.local && !self.network_contacts_url.is_empty() {
info!(
"Fetching bootstrap address from network contacts URLs: {:?}",
self.network_contacts_url
);
let addrs = self
.network_contacts_url
.iter()
.map(|url| url.parse::<Url>().map_err(|_| Error::FailedToParseUrl))
.collect::<Result<Vec<Url>>>()?;
let mut contacts_fetcher = ContactsFetcher::with_endpoints(addrs)?;
if let Some(count) = count {
contacts_fetcher.set_max_addrs(count);
}
let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
bootstrap_addresses.extend(addrs);
if let Some(count) = count {
if bootstrap_addresses.len() >= count {
bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
bootstrap_addresses.truncate(count);
info!(
"Found {} bootstrap addresses. Returning early.",
bootstrap_addresses.len()
);
return Ok(bootstrap_addresses);
}
}
}
if !self.local && get_network_id() == MAINNET_ID {
let mut contacts_fetcher = ContactsFetcher::with_mainnet_endpoints()?;
if let Some(count) = count {
contacts_fetcher.set_max_addrs(count);
}
info!("Fetching bootstrap address from mainnet contacts");
let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
bootstrap_addresses.extend(addrs);
} else if !self.local && get_network_id() == ALPHANET_ID {
let mut contacts_fetcher = ContactsFetcher::with_alphanet_endpoints()?;
if let Some(count) = count {
contacts_fetcher.set_max_addrs(count);
}
info!("Fetching bootstrap address from alphanet contacts");
let addrs = contacts_fetcher.fetch_bootstrap_addresses().await?;
bootstrap_addresses.extend(addrs);
}
if !bootstrap_addresses.is_empty() {
bootstrap_addresses.sort_by_key(|addr| addr.failure_rate() as u64);
if let Some(count) = count {
bootstrap_addresses.truncate(count);
}
info!(
"Found {} bootstrap addresses. Returning early.",
bootstrap_addresses.len()
);
Ok(bootstrap_addresses)
} else {
error!("No initial bootstrap peers found through any means");
Err(Error::NoBootstrapPeersFound)
}
}
pub fn read_addr_from_env() -> Vec<Multiaddr> {
Self::read_bootstrap_addr_from_env()
.into_iter()
.map(|addr| addr.addr)
.collect()
}
pub fn read_bootstrap_addr_from_env() -> Vec<BootstrapAddr> {
let mut bootstrap_addresses = Vec::new();
if let Ok(addrs) = std::env::var(ANT_PEERS_ENV) {
for addr_str in addrs.split(',') {
if let Some(addr) = craft_valid_multiaddr_from_str(addr_str, false) {
info!("Adding addr from environment variable: {addr}");
bootstrap_addresses.push(BootstrapAddr::new(addr));
} else {
warn!("Invalid multiaddress format from environment variable: {addr_str}");
}
}
}
bootstrap_addresses
}
pub fn get_bootstrap_cache_path(&self) -> Result<Option<PathBuf>> {
if let Some(dir) = &self.bootstrap_cache_dir {
if dir.is_file() {
return Err(Error::InvalidBootstrapCacheDir);
}
if !dir.exists() {
std::fs::create_dir_all(dir)?;
}
let path = dir.join(cache_file_name());
Ok(Some(path))
} else {
Ok(None)
}
}
}