tenzro_network/
discovery.rs1use libp2p::{
4 kad::{store::MemoryStore, Behaviour as Kademlia, Config as KademliaConfig, Mode},
5 Multiaddr, PeerId,
6};
7use std::time::Duration;
8use tenzro_types::network::NetworkRole;
9
10pub fn create_kademlia(local_peer_id: PeerId) -> Kademlia<MemoryStore> {
12 let mut config = KademliaConfig::new(libp2p::StreamProtocol::new("/tenzro/kad"));
13
14 config.set_query_timeout(Duration::from_secs(30));
21 config.set_replication_factor(std::num::NonZeroUsize::new(10).unwrap());
22 config.set_publication_interval(Some(Duration::from_secs(22 * 60 * 60)));
23 config.set_record_ttl(Some(Duration::from_secs(36 * 60 * 60)));
24 config.set_provider_record_ttl(Some(Duration::from_secs(24 * 60 * 60)));
25 config.disjoint_query_paths(true);
26
27 let store = MemoryStore::new(local_peer_id);
29 let mut kademlia = Kademlia::with_config(local_peer_id, store, config);
30
31 kademlia.set_mode(Some(Mode::Server));
33
34 kademlia
35}
36
37pub fn connect_to_bootstrap_nodes(
43 kademlia: &mut Kademlia<MemoryStore>,
44 config: &BootstrapConfig,
45) -> Vec<Multiaddr> {
46 let mut addrs_to_dial = Vec::new();
47
48 for addr in &config.boot_nodes {
49 let peer_id = addr.iter().find_map(|proto| {
51 if let libp2p::multiaddr::Protocol::P2p(peer_id) = proto {
52 Some(peer_id)
53 } else {
54 None
55 }
56 });
57
58 if let Some(peer_id) = peer_id {
59 kademlia.add_address(&peer_id, addr.clone());
61 tracing::debug!("Added boot node {} to Kademlia", peer_id);
62 }
63
64 addrs_to_dial.push(addr.clone());
65 }
66
67 if !config.boot_nodes.is_empty() {
69 if let Err(e) = kademlia.bootstrap() {
70 tracing::warn!("Failed to start DHT bootstrap: {:?}", e);
71 } else {
72 tracing::info!("Started DHT bootstrap with {} boot nodes", config.boot_nodes.len());
73 }
74 }
75
76 addrs_to_dial
77}
78
79pub fn bootstrap_dht(kademlia: &mut Kademlia<MemoryStore>, boot_nodes: Vec<(PeerId, Multiaddr)>) {
84 for (peer_id, addr) in boot_nodes {
85 kademlia.add_address(&peer_id, addr);
86 }
87
88 if let Err(e) = kademlia.bootstrap() {
90 tracing::warn!("Failed to start DHT bootstrap: {:?}", e);
91 } else {
92 tracing::info!("Started DHT bootstrap");
93 }
94}
95
96pub fn provider_key(provider_type: ProviderType) -> Vec<u8> {
98 match provider_type {
99 ProviderType::Inference => b"/tenzro/providers/inference".to_vec(),
100 ProviderType::Tee => b"/tenzro/providers/tee".to_vec(),
101 ProviderType::Storage => b"/tenzro/providers/storage".to_vec(),
102 ProviderType::Validator => b"/tenzro/providers/validator".to_vec(),
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum ProviderType {
109 Inference,
111 Tee,
113 Storage,
115 Validator,
117}
118
119impl ProviderType {
120 pub fn from_role(role: NetworkRole) -> Option<Self> {
122 match role {
123 NetworkRole::ModelProvider => Some(Self::Inference),
124 NetworkRole::TeeProvider => Some(Self::Tee),
125 NetworkRole::StorageProvider => Some(Self::Storage),
126 NetworkRole::Validator => Some(Self::Validator),
127 _ => None,
128 }
129 }
130
131 pub fn to_role(self) -> NetworkRole {
133 match self {
134 Self::Inference => NetworkRole::ModelProvider,
135 Self::Tee => NetworkRole::TeeProvider,
136 Self::Storage => NetworkRole::StorageProvider,
137 Self::Validator => NetworkRole::Validator,
138 }
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct BootstrapConfig {
145 pub boot_nodes: Vec<Multiaddr>,
147 pub enable_reconnect: bool,
149 pub reconnect_interval: Duration,
151}
152
153impl Default for BootstrapConfig {
154 fn default() -> Self {
155 Self {
156 boot_nodes: Vec::new(),
157 enable_reconnect: true,
158 reconnect_interval: Duration::from_secs(60),
159 }
160 }
161}
162
163impl BootstrapConfig {
164 pub fn new(boot_nodes: Vec<Multiaddr>) -> Self {
166 Self {
167 boot_nodes,
168 ..Default::default()
169 }
170 }
171
172 pub fn testnet() -> Self {
174 Self {
175 boot_nodes: vec![
176 "/dns4/testnet-boot-1.tenzro.network/tcp/9000".parse().unwrap(),
177 "/dns4/testnet-boot-2.tenzro.network/tcp/9000".parse().unwrap(),
178 ],
179 ..Default::default()
180 }
181 }
182
183 pub fn mainnet() -> Self {
185 Self {
186 boot_nodes: vec![
187 "/dns4/mainnet-boot-1.tenzro.network/tcp/9000".parse().unwrap(),
188 "/dns4/mainnet-boot-2.tenzro.network/tcp/9000".parse().unwrap(),
189 "/dns4/mainnet-boot-3.tenzro.network/tcp/9000".parse().unwrap(),
190 ],
191 ..Default::default()
192 }
193 }
194}
195
196#[derive(Debug, Clone)]
198pub struct DiscoveryConfig {
199 pub enable_random_walk: bool,
201 pub random_walk_interval: Duration,
203 pub enable_provider_announcement: bool,
205 pub provider_announcement_interval: Duration,
207}
208
209impl Default for DiscoveryConfig {
210 fn default() -> Self {
211 Self {
212 enable_random_walk: true,
213 random_walk_interval: Duration::from_secs(300), enable_provider_announcement: false,
215 provider_announcement_interval: Duration::from_secs(600), }
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_kademlia_creation() {
226 let peer_id = PeerId::random();
227 let kad = create_kademlia(peer_id);
228 drop(kad);
230 }
231
232 #[test]
233 fn test_provider_keys() {
234 let inference_key = provider_key(ProviderType::Inference);
235 let tee_key = provider_key(ProviderType::Tee);
236
237 assert_ne!(inference_key, tee_key);
238 assert_eq!(inference_key, b"/tenzro/providers/inference");
239 }
240
241 #[test]
242 fn test_provider_type_conversion() {
243 assert_eq!(
244 ProviderType::from_role(NetworkRole::ModelProvider),
245 Some(ProviderType::Inference)
246 );
247 assert_eq!(
248 ProviderType::from_role(NetworkRole::FullNode),
249 None
250 );
251 }
252
253 #[test]
254 fn test_bootstrap_config_default() {
255 let config = BootstrapConfig::default();
256 assert!(config.boot_nodes.is_empty());
257 assert!(config.enable_reconnect);
258 assert_eq!(config.reconnect_interval, Duration::from_secs(60));
259 }
260
261 #[test]
262 fn test_bootstrap_config_testnet() {
263 let config = BootstrapConfig::testnet();
264 assert_eq!(config.boot_nodes.len(), 2);
265 assert!(config.enable_reconnect);
266 }
267
268 #[test]
269 fn test_bootstrap_config_mainnet() {
270 let config = BootstrapConfig::mainnet();
271 assert_eq!(config.boot_nodes.len(), 3);
272 assert!(config.enable_reconnect);
273 }
274
275 #[test]
276 fn test_connect_to_bootstrap_nodes() {
277 let peer_id = PeerId::random();
278 let mut kad = create_kademlia(peer_id);
279
280 let boot_peer = PeerId::random();
282 let boot_addr: Multiaddr = format!("/ip4/127.0.0.1/tcp/9000/p2p/{}", boot_peer)
283 .parse()
284 .unwrap();
285
286 let config = BootstrapConfig::new(vec![boot_addr.clone()]);
287 let addrs = connect_to_bootstrap_nodes(&mut kad, &config);
288
289 assert_eq!(addrs.len(), 1);
290 assert_eq!(addrs[0], boot_addr);
291 }
292
293 #[test]
294 fn test_connect_to_bootstrap_nodes_empty() {
295 let peer_id = PeerId::random();
296 let mut kad = create_kademlia(peer_id);
297
298 let config = BootstrapConfig::default();
299 let addrs = connect_to_bootstrap_nodes(&mut kad, &config);
300
301 assert!(addrs.is_empty());
302 }
303}