bittensor_rs/
discovery.rs1use anyhow::Result;
7use std::collections::HashMap;
8use std::net::SocketAddr;
9use tracing::{debug, info, warn};
10
11use crate::Metagraph;
12
13#[derive(Debug, Clone)]
15pub struct NeuronInfo {
16 pub uid: u16,
18 pub hotkey: String,
20 pub coldkey: String,
22 pub stake: u64,
24 pub is_validator: bool,
26 pub axon_info: Option<AxonInfo>,
28}
29
30#[derive(Debug, Clone)]
32pub struct AxonInfo {
33 pub ip: String,
35 pub port: u16,
37 pub version: u32,
39 pub socket_addr: SocketAddr,
41}
42
43pub struct NeuronDiscovery<'a> {
45 metagraph: &'a Metagraph,
46}
47
48impl<'a> NeuronDiscovery<'a> {
49 pub fn new(metagraph: &'a Metagraph) -> Self {
51 Self { metagraph }
52 }
53
54 pub fn get_all_neurons(&self) -> Result<Vec<NeuronInfo>> {
56 let mut neurons = Vec::new();
57
58 for (idx, hotkey) in self.metagraph.hotkeys.iter().enumerate() {
60 let uid = idx as u16;
61
62 if let Some(axon_info) = self.extract_axon_info(uid) {
63 debug!(
64 "Found neuron UID {} with axon endpoint {}:{}",
65 uid, axon_info.ip, axon_info.port
66 );
67 }
68
69 let coldkey = self
71 .metagraph
72 .coldkeys
73 .get(idx)
74 .map(|c| c.to_string())
75 .unwrap_or_default();
76
77 let stake = self
78 .metagraph
79 .total_stake
80 .get(idx)
81 .map(|s| s.0)
82 .unwrap_or(0);
83
84 let is_validator = self
85 .metagraph
86 .validator_permit
87 .get(idx)
88 .copied()
89 .unwrap_or(false);
90
91 neurons.push(NeuronInfo {
92 uid,
93 hotkey: hotkey.to_string(),
94 coldkey,
95 stake,
96 is_validator,
97 axon_info: self.extract_axon_info(uid),
98 });
99 }
100
101 info!("Discovered {} neurons from metagraph", neurons.len());
102 Ok(neurons)
103 }
104
105 pub fn get_validators(&self) -> Result<Vec<NeuronInfo>> {
107 let all_neurons = self.get_all_neurons()?;
108 let validators: Vec<NeuronInfo> =
109 all_neurons.into_iter().filter(|n| n.is_validator).collect();
110
111 info!("Found {} validators in metagraph", validators.len());
112 Ok(validators)
113 }
114
115 pub fn get_miners(&self) -> Result<Vec<NeuronInfo>> {
117 let all_neurons = self.get_all_neurons()?;
118 let miners: Vec<NeuronInfo> = all_neurons
119 .into_iter()
120 .filter(|n| !n.is_validator)
121 .collect();
122
123 info!("Found {} miners in metagraph", miners.len());
124 Ok(miners)
125 }
126
127 pub fn get_neurons_with_axons(&self) -> Result<Vec<NeuronInfo>> {
129 let all_neurons = self.get_all_neurons()?;
130 let with_axons: Vec<NeuronInfo> = all_neurons
131 .into_iter()
132 .filter(|n| n.axon_info.is_some())
133 .collect();
134
135 info!("Found {} neurons with axon endpoints", with_axons.len());
136 Ok(with_axons)
137 }
138
139 pub fn find_neuron_by_hotkey(&self, hotkey: &str) -> Option<NeuronInfo> {
141 for (idx, h) in self.metagraph.hotkeys.iter().enumerate() {
142 if h.to_string() == hotkey {
143 let uid = idx as u16;
144 let coldkey = self
145 .metagraph
146 .coldkeys
147 .get(idx)
148 .map(|c| c.to_string())
149 .unwrap_or_default();
150
151 let stake = self
152 .metagraph
153 .total_stake
154 .get(idx)
155 .map(|s| s.0)
156 .unwrap_or(0);
157
158 let is_validator = self
159 .metagraph
160 .validator_permit
161 .get(idx)
162 .copied()
163 .unwrap_or(false);
164
165 return Some(NeuronInfo {
166 uid,
167 hotkey: h.to_string(),
168 coldkey,
169 stake,
170 is_validator,
171 axon_info: self.extract_axon_info(uid),
172 });
173 }
174 }
175 None
176 }
177
178 pub fn find_neuron_by_uid(&self, uid: u16) -> Option<NeuronInfo> {
180 let idx = uid as usize;
181 if idx >= self.metagraph.hotkeys.len() {
182 return None;
183 }
184
185 let hotkey = self.metagraph.hotkeys.get(idx)?;
186 let coldkey = self
187 .metagraph
188 .coldkeys
189 .get(idx)
190 .map(|c| c.to_string())
191 .unwrap_or_default();
192
193 let stake = self
194 .metagraph
195 .total_stake
196 .get(idx)
197 .map(|s| s.0)
198 .unwrap_or(0);
199
200 let is_validator = self
201 .metagraph
202 .validator_permit
203 .get(idx)
204 .copied()
205 .unwrap_or(false);
206
207 Some(NeuronInfo {
208 uid,
209 hotkey: hotkey.to_string(),
210 coldkey,
211 stake,
212 is_validator,
213 axon_info: self.extract_axon_info(uid),
214 })
215 }
216
217 pub fn is_hotkey_registered(&self, hotkey: &str) -> bool {
219 self.find_neuron_by_hotkey(hotkey).is_some()
220 }
221
222 pub fn get_neurons_by_min_stake(&self, min_stake: u64) -> Result<Vec<NeuronInfo>> {
224 let all_neurons = self.get_all_neurons()?;
225 let filtered: Vec<NeuronInfo> = all_neurons
226 .into_iter()
227 .filter(|n| n.stake >= min_stake)
228 .collect();
229
230 info!(
231 "Found {} neurons with stake >= {}",
232 filtered.len(),
233 min_stake
234 );
235 Ok(filtered)
236 }
237
238 pub fn extract_axon_info(&self, uid: u16) -> Option<AxonInfo> {
240 self.metagraph.axons.get(uid as usize).and_then(|axon| {
241 if axon.ip == 0 || axon.port == 0 {
243 return None;
244 }
245
246 let ip_str = if axon.ip_type == 4 {
248 let ipv4_bits = axon.ip as u32;
250 let ip_bytes = ipv4_bits.to_be_bytes();
251 format!(
252 "{}.{}.{}.{}",
253 ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]
254 )
255 } else {
256 format!("{:x}", axon.ip)
258 };
259
260 if ip_str == "0.0.0.0" || ip_str == "127.0.0.1" {
262 debug!("Skipping invalid axon IP {} for UID {}", ip_str, uid);
263 return None;
264 }
265
266 match format!("{}:{}", ip_str, axon.port).parse::<SocketAddr>() {
268 Ok(socket_addr) => Some(AxonInfo {
269 ip: ip_str,
270 port: axon.port,
271 version: axon.version,
272 socket_addr,
273 }),
274 Err(e) => {
275 warn!(
276 "Failed to parse socket address for UID {}: {}:{} - {}",
277 uid, ip_str, axon.port, e
278 );
279 None
280 }
281 }
282 })
283 }
284}
285
286pub fn create_hotkey_to_uid_map(metagraph: &Metagraph) -> HashMap<String, u16> {
288 let mut map = HashMap::new();
289 for (idx, hotkey) in metagraph.hotkeys.iter().enumerate() {
290 map.insert(hotkey.to_string(), idx as u16);
291 }
292 map
293}
294
295pub fn create_uid_to_axon_map(metagraph: &Metagraph) -> HashMap<u16, SocketAddr> {
297 let discovery = NeuronDiscovery::new(metagraph);
298 let mut map = HashMap::new();
299
300 for idx in 0..metagraph.hotkeys.len() {
301 let uid = idx as u16;
302 if let Some(axon_info) = discovery.extract_axon_info(uid) {
303 map.insert(uid, axon_info.socket_addr);
304 }
305 }
306 map
307}
308
309#[cfg(test)]
310mod tests {
311
312 #[test]
313 fn test_ip_conversion() {
314 let ip_u32: u32 = 0xC0A80101; let ip_bytes = ip_u32.to_be_bytes();
317 let ip_str = format!(
318 "{}.{}.{}.{}",
319 ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]
320 );
321 assert_eq!(ip_str, "192.168.1.1");
322 }
323}