use libp2p::{
kad::{store::MemoryStore, Behaviour as Kademlia, Config as KademliaConfig, Mode},
Multiaddr, PeerId,
};
use std::time::Duration;
use tenzro_types::network::NetworkRole;
pub fn create_kademlia(local_peer_id: PeerId) -> Kademlia<MemoryStore> {
let mut config = KademliaConfig::new(libp2p::StreamProtocol::new("/tenzro/kad"));
config.set_query_timeout(Duration::from_secs(30));
config.set_replication_factor(std::num::NonZeroUsize::new(10).unwrap());
config.set_publication_interval(Some(Duration::from_secs(22 * 60 * 60)));
config.set_record_ttl(Some(Duration::from_secs(36 * 60 * 60)));
config.set_provider_record_ttl(Some(Duration::from_secs(24 * 60 * 60)));
config.disjoint_query_paths(true);
let store = MemoryStore::new(local_peer_id);
let mut kademlia = Kademlia::with_config(local_peer_id, store, config);
kademlia.set_mode(Some(Mode::Server));
kademlia
}
pub fn connect_to_bootstrap_nodes(
kademlia: &mut Kademlia<MemoryStore>,
config: &BootstrapConfig,
) -> Vec<Multiaddr> {
let mut addrs_to_dial = Vec::new();
for addr in &config.boot_nodes {
let peer_id = addr.iter().find_map(|proto| {
if let libp2p::multiaddr::Protocol::P2p(peer_id) = proto {
Some(peer_id)
} else {
None
}
});
if let Some(peer_id) = peer_id {
kademlia.add_address(&peer_id, addr.clone());
tracing::debug!("Added boot node {} to Kademlia", peer_id);
}
addrs_to_dial.push(addr.clone());
}
if !config.boot_nodes.is_empty() {
if let Err(e) = kademlia.bootstrap() {
tracing::warn!("Failed to start DHT bootstrap: {:?}", e);
} else {
tracing::info!("Started DHT bootstrap with {} boot nodes", config.boot_nodes.len());
}
}
addrs_to_dial
}
pub fn bootstrap_dht(kademlia: &mut Kademlia<MemoryStore>, boot_nodes: Vec<(PeerId, Multiaddr)>) {
for (peer_id, addr) in boot_nodes {
kademlia.add_address(&peer_id, addr);
}
if let Err(e) = kademlia.bootstrap() {
tracing::warn!("Failed to start DHT bootstrap: {:?}", e);
} else {
tracing::info!("Started DHT bootstrap");
}
}
pub fn provider_key(provider_type: ProviderType) -> Vec<u8> {
match provider_type {
ProviderType::Inference => b"/tenzro/providers/inference".to_vec(),
ProviderType::Tee => b"/tenzro/providers/tee".to_vec(),
ProviderType::Storage => b"/tenzro/providers/storage".to_vec(),
ProviderType::Validator => b"/tenzro/providers/validator".to_vec(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderType {
Inference,
Tee,
Storage,
Validator,
}
impl ProviderType {
pub fn from_role(role: NetworkRole) -> Option<Self> {
match role {
NetworkRole::ModelProvider => Some(Self::Inference),
NetworkRole::TeeProvider => Some(Self::Tee),
NetworkRole::StorageProvider => Some(Self::Storage),
NetworkRole::Validator => Some(Self::Validator),
_ => None,
}
}
pub fn to_role(self) -> NetworkRole {
match self {
Self::Inference => NetworkRole::ModelProvider,
Self::Tee => NetworkRole::TeeProvider,
Self::Storage => NetworkRole::StorageProvider,
Self::Validator => NetworkRole::Validator,
}
}
}
#[derive(Debug, Clone)]
pub struct BootstrapConfig {
pub boot_nodes: Vec<Multiaddr>,
pub enable_reconnect: bool,
pub reconnect_interval: Duration,
}
impl Default for BootstrapConfig {
fn default() -> Self {
Self {
boot_nodes: Vec::new(),
enable_reconnect: true,
reconnect_interval: Duration::from_secs(60),
}
}
}
impl BootstrapConfig {
pub fn new(boot_nodes: Vec<Multiaddr>) -> Self {
Self {
boot_nodes,
..Default::default()
}
}
pub fn testnet() -> Self {
Self {
boot_nodes: vec![
"/dns4/testnet-boot-1.tenzro.network/tcp/9000".parse().unwrap(),
"/dns4/testnet-boot-2.tenzro.network/tcp/9000".parse().unwrap(),
],
..Default::default()
}
}
pub fn mainnet() -> Self {
Self {
boot_nodes: vec![
"/dns4/mainnet-boot-1.tenzro.network/tcp/9000".parse().unwrap(),
"/dns4/mainnet-boot-2.tenzro.network/tcp/9000".parse().unwrap(),
"/dns4/mainnet-boot-3.tenzro.network/tcp/9000".parse().unwrap(),
],
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct DiscoveryConfig {
pub enable_random_walk: bool,
pub random_walk_interval: Duration,
pub enable_provider_announcement: bool,
pub provider_announcement_interval: Duration,
}
impl Default for DiscoveryConfig {
fn default() -> Self {
Self {
enable_random_walk: true,
random_walk_interval: Duration::from_secs(300), enable_provider_announcement: false,
provider_announcement_interval: Duration::from_secs(600), }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kademlia_creation() {
let peer_id = PeerId::random();
let kad = create_kademlia(peer_id);
drop(kad);
}
#[test]
fn test_provider_keys() {
let inference_key = provider_key(ProviderType::Inference);
let tee_key = provider_key(ProviderType::Tee);
assert_ne!(inference_key, tee_key);
assert_eq!(inference_key, b"/tenzro/providers/inference");
}
#[test]
fn test_provider_type_conversion() {
assert_eq!(
ProviderType::from_role(NetworkRole::ModelProvider),
Some(ProviderType::Inference)
);
assert_eq!(
ProviderType::from_role(NetworkRole::FullNode),
None
);
}
#[test]
fn test_bootstrap_config_default() {
let config = BootstrapConfig::default();
assert!(config.boot_nodes.is_empty());
assert!(config.enable_reconnect);
assert_eq!(config.reconnect_interval, Duration::from_secs(60));
}
#[test]
fn test_bootstrap_config_testnet() {
let config = BootstrapConfig::testnet();
assert_eq!(config.boot_nodes.len(), 2);
assert!(config.enable_reconnect);
}
#[test]
fn test_bootstrap_config_mainnet() {
let config = BootstrapConfig::mainnet();
assert_eq!(config.boot_nodes.len(), 3);
assert!(config.enable_reconnect);
}
#[test]
fn test_connect_to_bootstrap_nodes() {
let peer_id = PeerId::random();
let mut kad = create_kademlia(peer_id);
let boot_peer = PeerId::random();
let boot_addr: Multiaddr = format!("/ip4/127.0.0.1/tcp/9000/p2p/{}", boot_peer)
.parse()
.unwrap();
let config = BootstrapConfig::new(vec![boot_addr.clone()]);
let addrs = connect_to_bootstrap_nodes(&mut kad, &config);
assert_eq!(addrs.len(), 1);
assert_eq!(addrs[0], boot_addr);
}
#[test]
fn test_connect_to_bootstrap_nodes_empty() {
let peer_id = PeerId::random();
let mut kad = create_kademlia(peer_id);
let config = BootstrapConfig::default();
let addrs = connect_to_bootstrap_nodes(&mut kad, &config);
assert!(addrs.is_empty());
}
}