asic_rs/miners/backends/epic/
mod.rs

1use std::collections::HashMap;
2use std::net::IpAddr;
3use std::str::FromStr;
4use std::time::Duration;
5
6use macaddr::MacAddr;
7use measurements::{AngularVelocity, Frequency, Power, Temperature, Voltage};
8use serde_json::Value;
9
10use crate::data::board::{BoardData, ChipData};
11use crate::data::device::MinerMake;
12use crate::data::device::{DeviceInfo, HashAlgorithm, MinerFirmware, MinerModel};
13use crate::data::fan::FanData;
14use crate::data::hashrate::{HashRate, HashRateUnit};
15use crate::data::pool::{PoolData, PoolURL};
16use crate::miners::backends::traits::*;
17use crate::miners::commands::MinerCommand;
18use crate::miners::data::{
19    DataCollector, DataExtensions, DataExtractor, DataField, DataLocation, get_by_pointer,
20};
21use web::PowerPlayWebAPI;
22
23pub mod web;
24
25#[derive(Debug)]
26pub struct PowerPlay {
27    ip: IpAddr,
28    web: PowerPlayWebAPI,
29    device_info: DeviceInfo,
30}
31
32impl PowerPlay {
33    pub fn new(ip: IpAddr, make: MinerMake, model: MinerModel) -> Self {
34        PowerPlay {
35            ip,
36            web: PowerPlayWebAPI::new(ip, 4028),
37            device_info: DeviceInfo::new(make, model, MinerFirmware::EPic, HashAlgorithm::SHA256),
38        }
39    }
40}
41
42impl GetDataLocations for PowerPlay {
43    fn get_locations(&self, data_field: DataField) -> Vec<DataLocation> {
44        fn cmd(endpoint: &'static str) -> MinerCommand {
45            MinerCommand::WebAPI {
46                command: endpoint,
47                parameters: None,
48            }
49        }
50
51        let summary_cmd = cmd("summary");
52        let network_cmd = cmd("network");
53        let capabilities_cmd = cmd("capabilities");
54        let chip_temps_cmd = cmd("temps/chip");
55        let chip_voltages_cmd = cmd("voltages");
56        let chip_hashrates_cmd = cmd("hashrate");
57        let chip_clocks_cmd = cmd("clocks");
58        let temps_cmd = cmd("temps");
59
60        match data_field {
61            DataField::Mac => vec![(
62                network_cmd,
63                DataExtractor {
64                    func: get_by_pointer,
65                    key: Some(""),
66                    tag: None,
67                },
68            )],
69            DataField::Hostname => vec![(
70                summary_cmd,
71                DataExtractor {
72                    func: get_by_pointer,
73                    key: Some("/Hostname"),
74                    tag: None,
75                },
76            )],
77            DataField::Uptime => vec![(
78                summary_cmd,
79                DataExtractor {
80                    func: get_by_pointer,
81                    key: Some("/Session/Uptime"),
82                    tag: None,
83                },
84            )],
85            DataField::Wattage => vec![(
86                summary_cmd,
87                DataExtractor {
88                    func: get_by_pointer,
89                    key: Some("/Power Supply Stats/Input Power"),
90                    tag: None,
91                },
92            )],
93            DataField::Fans => vec![(
94                summary_cmd,
95                DataExtractor {
96                    func: get_by_pointer,
97                    key: Some("/Fans Rpm"),
98                    tag: None,
99                },
100            )],
101            DataField::Hashboards => vec![
102                (
103                    temps_cmd,
104                    DataExtractor {
105                        func: get_by_pointer,
106                        key: Some(""),
107                        tag: Some("Board Temps"),
108                    },
109                ),
110                (
111                    summary_cmd,
112                    DataExtractor {
113                        func: get_by_pointer,
114                        key: Some(""),
115                        tag: Some("Summary"),
116                    },
117                ),
118                (
119                    chip_temps_cmd,
120                    DataExtractor {
121                        func: get_by_pointer,
122                        key: Some(""),
123                        tag: Some("Chip Temps"),
124                    },
125                ),
126                (
127                    chip_voltages_cmd,
128                    DataExtractor {
129                        func: get_by_pointer,
130                        key: Some(""),
131                        tag: Some("Chip Voltages"),
132                    },
133                ),
134                (
135                    chip_hashrates_cmd,
136                    DataExtractor {
137                        func: get_by_pointer,
138                        key: Some(""),
139                        tag: Some("Chip Hashrates"),
140                    },
141                ),
142                (
143                    chip_clocks_cmd,
144                    DataExtractor {
145                        func: get_by_pointer,
146                        key: Some(""),
147                        tag: Some("Chip Clocks"),
148                    },
149                ),
150                (
151                    capabilities_cmd,
152                    DataExtractor {
153                        func: get_by_pointer,
154                        key: Some(""),
155                        tag: Some("Capabilities"),
156                    },
157                ),
158            ],
159            DataField::Pools => vec![(
160                summary_cmd,
161                DataExtractor {
162                    func: get_by_pointer,
163                    key: Some(""),
164                    tag: None,
165                },
166            )],
167            DataField::IsMining => vec![(
168                summary_cmd,
169                DataExtractor {
170                    func: get_by_pointer,
171                    key: Some("/Status/Operating State"),
172                    tag: None,
173                },
174            )],
175            DataField::LightFlashing => vec![(
176                summary_cmd,
177                DataExtractor {
178                    func: get_by_pointer,
179                    key: Some("/Misc/Locate Miner State"),
180                    tag: None,
181                },
182            )],
183            DataField::ControlBoardVersion => vec![(
184                capabilities_cmd,
185                DataExtractor {
186                    func: get_by_pointer,
187                    key: Some("/Control Board Version/cpuHardware"),
188                    tag: None,
189                },
190            )],
191            DataField::SerialNumber => vec![(
192                capabilities_cmd,
193                DataExtractor {
194                    func: get_by_pointer,
195                    key: Some("/Control Board Version/cpuSerial"),
196                    tag: None,
197                },
198            )],
199            DataField::ExpectedHashrate => vec![(
200                capabilities_cmd,
201                DataExtractor {
202                    func: get_by_pointer,
203                    key: Some("/Default Hashrate"),
204                    tag: None,
205                },
206            )],
207            DataField::FirmwareVersion => vec![(
208                summary_cmd,
209                DataExtractor {
210                    func: get_by_pointer,
211                    key: Some("/Software"),
212                    tag: None,
213                },
214            )],
215            DataField::Hashrate => vec![(
216                summary_cmd,
217                DataExtractor {
218                    func: get_by_pointer,
219                    key: Some("/HBs"),
220                    tag: None,
221                },
222            )],
223            _ => vec![],
224        }
225    }
226}
227
228impl GetIP for PowerPlay {
229    fn get_ip(&self) -> IpAddr {
230        self.ip
231    }
232}
233
234impl GetDeviceInfo for PowerPlay {
235    fn get_device_info(&self) -> DeviceInfo {
236        self.device_info
237    }
238}
239
240impl CollectData for PowerPlay {
241    fn get_collector(&self) -> DataCollector<'_> {
242        DataCollector::new(self, &self.web)
243    }
244}
245
246impl GetMAC for PowerPlay {
247    fn parse_mac(&self, data: &HashMap<DataField, Value>) -> Option<MacAddr> {
248        match serde_json::from_value::<HashMap<String, Value>>(data.get(&DataField::Mac)?.clone())
249            .ok()
250            .and_then(|inner| inner.get("dhcp").or_else(|| inner.get("static")).cloned())
251            .and_then(|obj| {
252                obj.get("mac_address")
253                    .and_then(|v| v.as_str())
254                    .map(String::from)
255            }) {
256            Some(mac_str) => MacAddr::from_str(&mac_str).ok(),
257            None => None,
258        }
259    }
260}
261
262impl GetSerialNumber for PowerPlay {
263    fn parse_serial_number(&self, data: &HashMap<DataField, Value>) -> Option<String> {
264        data.extract::<String>(DataField::SerialNumber)
265    }
266}
267
268impl GetHostname for PowerPlay {
269    fn parse_hostname(&self, data: &HashMap<DataField, Value>) -> Option<String> {
270        data.extract::<String>(DataField::Hostname)
271    }
272}
273
274impl GetApiVersion for PowerPlay {
275    fn parse_api_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
276        data.extract::<String>(DataField::ApiVersion)
277    }
278}
279
280impl GetFirmwareVersion for PowerPlay {
281    fn parse_firmware_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
282        data.extract::<String>(DataField::FirmwareVersion)
283    }
284}
285
286impl GetControlBoardVersion for PowerPlay {
287    fn parse_control_board_version(&self, data: &HashMap<DataField, Value>) -> Option<String> {
288        data.extract::<String>(DataField::ControlBoardVersion)
289    }
290}
291
292impl GetHashboards for PowerPlay {
293    fn parse_hashboards(&self, data: &HashMap<DataField, Value>) -> Vec<BoardData> {
294        let mut hashboards: Vec<BoardData> = Vec::new();
295        for _ in 0..self.device_info.hardware.boards.unwrap_or_default() {
296            hashboards.push(BoardData {
297                position: 0,
298                hashrate: None,
299                expected_hashrate: None,
300                board_temperature: None,
301                intake_temperature: None,
302                outlet_temperature: None,
303                expected_chips: None,
304                working_chips: None,
305                serial_number: None,
306                chips: vec![],
307                voltage: None,
308                frequency: None,
309                tuned: None,
310                active: None,
311            });
312        }
313
314        data.get(&DataField::Hashboards)
315            .and_then(|v| v.pointer("/Summary/HBStatus"))
316            .and_then(|v| {
317                v.as_array().map(|boards| {
318                    boards.iter().for_each(|board| {
319                        if let Some(idx) = board.get("Index").and_then(|v| v.as_u64())
320                            && let Some(hashboard) = hashboards.get_mut(idx as usize)
321                        {
322                            hashboard.position = idx as u8;
323                            if let Some(v) = board.get("Enabled").and_then(|v| v.as_bool()) {
324                                hashboard.active = Some(v);
325                            }
326                        }
327                    })
328                })
329            });
330
331        // Create ChipData for each active board
332        for board in &mut hashboards {
333            board.expected_chips = self.device_info.hardware.chips;
334            // No need to add ChipData if we know the board is not active
335            if board.active.unwrap_or(false) {
336                board.chips = vec![
337                    ChipData {
338                        position: 0,
339                        hashrate: None,
340                        temperature: None,
341                        voltage: None,
342                        frequency: None,
343                        tuned: None,
344                        working: None,
345                    };
346                    self.device_info.hardware.chips.unwrap_or_default() as usize
347                ];
348            }
349        }
350
351        //Capabilities Board Serial Numbers
352        if let Some(serial_numbers) = data
353            .get(&DataField::Hashboards)
354            .and_then(|v| v.pointer("/Capabilities/Board Serial Numbers"))
355            .and_then(|v| v.as_array())
356        {
357            for serial in serial_numbers {
358                // Since we only have an array with no index, it will only correspond to working boards, so search for first working board
359                // without serial and insert there
360                for hb in hashboards.iter_mut() {
361                    if hb.serial_number.is_none() && hb.active.unwrap_or(false) {
362                        if let Some(serial_str) = serial.as_str() {
363                            hb.serial_number = Some(serial_str.to_string());
364                        }
365                        break; // Only assign to the first board without a serial number
366                    }
367                }
368            }
369        };
370
371        // Summary Data
372        data.get(&DataField::Hashboards)
373            .and_then(|v| v.pointer("/Summary/HBs"))
374            .and_then(|v| {
375                v.as_array().map(|boards| {
376                    boards.iter().for_each(|board| {
377                        if let Some(idx) = board.get("Index").and_then(|v| v.as_u64())
378                            && let Some(hashboard) = hashboards.get_mut(idx as usize)
379                        {
380                            // Hashrate
381                            if let Some(h) = board
382                                .get("Hashrate")
383                                .and_then(|v| v.as_array())
384                                .and_then(|v| v.first().and_then(|f| f.as_f64()))
385                            {
386                                hashboard.hashrate = Some(HashRate {
387                                    value: h,
388                                    unit: HashRateUnit::MegaHash,
389                                    algo: String::from("SHA256"),
390                                })
391                            };
392
393                            // ExpectedHashrate
394                            if let Some(h) = board
395                                .get("Hashrate")
396                                .and_then(|v| v.as_array())
397                                .and_then(|v| {
398                                    Some((
399                                        v.first().and_then(|f| f.as_f64())?,
400                                        v.get(1).and_then(|f| f.as_f64())?,
401                                    ))
402                                })
403                            {
404                                hashboard.expected_hashrate = Some(HashRate {
405                                    value: h.0 / h.1,
406                                    unit: HashRateUnit::MegaHash,
407                                    algo: String::from("SHA256"),
408                                })
409                            };
410
411                            //Frequency
412                            if let Some(f) = board.get("Core Clock Avg").and_then(|v| v.as_f64()) {
413                                hashboard.frequency = Some(Frequency::from_megahertz(f))
414                            };
415
416                            //Voltage
417                            if let Some(v) = board.get("Input Voltage").and_then(|v| v.as_f64()) {
418                                hashboard.voltage = Some(Voltage::from_volts(v));
419                            };
420                            //Board Temp
421                            if let Some(v) = board.get("Temperature").and_then(|v| v.as_f64()) {
422                                hashboard.board_temperature = Some(Temperature::from_celsius(v));
423                            };
424                        };
425                    })
426                })
427            });
428
429        //Temp Data
430        data.get(&DataField::Hashboards)
431            .and_then(|v| v.pointer("/Board Temps"))
432            .and_then(|v| {
433                v.as_array().map(|boards| {
434                    boards.iter().for_each(|board| {
435                        if let Some(idx) = board.get("Index").and_then(|v| v.as_u64())
436                            && let Some(hashboard) = hashboards.get_mut(idx as usize)
437                        {
438                            // Outlet Temperature
439                            if let Some(h) = board.get("Data").and_then(|v| {
440                                v.as_array().and_then(|arr| {
441                                    arr.iter()
442                                        .filter_map(|v| v.as_f64())
443                                        .max_by(|a, b| a.partial_cmp(b).unwrap())
444                                })
445                            }) {
446                                hashboard.outlet_temperature = Some(Temperature::from_celsius(h));
447                            };
448
449                            if let Some(h) = board.get("Data").and_then(|v| {
450                                v.as_array().and_then(|arr| {
451                                    arr.iter()
452                                        .filter_map(|v| v.as_f64())
453                                        .min_by(|a, b| a.partial_cmp(b).unwrap())
454                                })
455                            }) {
456                                hashboard.intake_temperature = Some(Temperature::from_celsius(h));
457                            };
458                        };
459                    })
460                })
461            });
462
463        //Chip Temps
464        data.get(&DataField::Hashboards)
465            .and_then(|v| v.pointer("/Chip Temps"))
466            .and_then(|v| {
467                v.as_array().map(|boards| {
468                    boards.iter().for_each(|board| {
469                        if let Some(idx) = board.get("Index").and_then(|v| v.as_u64())
470                            && let Some(hashboard) = hashboards.get_mut(idx as usize)
471                            && let Some(t) =
472                                board.get("Data").and_then(|v| v.as_array()).map(|arr| {
473                                    arr.iter()
474                                        .filter_map(|v| v.as_f64())
475                                        .map(Temperature::from_celsius)
476                                        .collect::<Vec<Temperature>>()
477                                })
478                        {
479                            for (chip_no, temp) in t.iter().enumerate() {
480                                if let Some(chip_data) = hashboard.chips.get_mut(chip_no) {
481                                    chip_data.position = chip_no as u16;
482                                    chip_data.temperature = Some(*temp);
483                                }
484                            }
485                        };
486                    })
487                })
488            });
489
490        //Chip Voltages
491        data.get(&DataField::Hashboards)
492            .and_then(|v| v.pointer("/Chip Voltages"))
493            .and_then(|v| {
494                v.as_array().map(|boards| {
495                    boards.iter().for_each(|board| {
496                        if let Some(idx) = board.get("Index").and_then(|v| v.as_u64())
497                            && let Some(hashboard) = hashboards.get_mut(idx as usize)
498                            && let Some(t) =
499                                board.get("Data").and_then(|v| v.as_array()).map(|arr| {
500                                    arr.iter()
501                                        .filter_map(|v| v.as_f64())
502                                        .map(Voltage::from_millivolts)
503                                        .collect::<Vec<Voltage>>()
504                                })
505                        {
506                            for (chip_no, voltage) in t.iter().enumerate() {
507                                if let Some(chip_data) = hashboard.chips.get_mut(chip_no) {
508                                    chip_data.position = chip_no as u16;
509                                    chip_data.voltage = Some(*voltage);
510                                }
511                            }
512                        };
513                    })
514                })
515            });
516
517        //Chip Frequencies
518        data.get(&DataField::Hashboards)
519            .and_then(|v| v.pointer("/Chip Clocks"))
520            .and_then(|v| {
521                v.as_array().map(|boards| {
522                    boards.iter().for_each(|board| {
523                        if let Some(idx) = board.get("Index").and_then(|v| v.as_u64())
524                            && let Some(hashboard) = hashboards.get_mut(idx as usize)
525                            && let Some(t) =
526                                board.get("Data").and_then(|v| v.as_array()).map(|arr| {
527                                    arr.iter()
528                                        .filter_map(|v| v.as_f64())
529                                        .map(Frequency::from_megahertz)
530                                        .collect::<Vec<Frequency>>()
531                                })
532                        {
533                            for (chip_no, freq) in t.iter().enumerate() {
534                                if let Some(chip_data) = hashboard.chips.get_mut(chip_no) {
535                                    chip_data.position = chip_no as u16;
536                                    chip_data.frequency = Some(*freq);
537                                }
538                            }
539                        };
540                    })
541                })
542            });
543
544        //Chip Hashrate
545        //There should always be a hashrate, and if there is a hashrate its also working
546        data.get(&DataField::Hashboards)
547            .and_then(|v| v.pointer("/Chip Hashrates"))
548            .and_then(|v| {
549                v.as_array().map(|boards| {
550                    boards.iter().for_each(|board| {
551                        if let Some(idx) = board.get("Index").and_then(|v| v.as_u64())
552                            && let Some(hashboard) = hashboards.get_mut(idx as usize)
553                            && let Some(t) =
554                                board.get("Data").and_then(|v| v.as_array()).map(|arr| {
555                                    arr.iter()
556                                        .filter_map(|inner| inner.as_array())
557                                        .filter_map(|inner| inner.first().and_then(|v| v.as_f64()))
558                                        .map(|hr| HashRate {
559                                            value: hr,
560                                            unit: HashRateUnit::MegaHash,
561                                            algo: String::from("SHA256"),
562                                        })
563                                        .collect::<Vec<HashRate>>()
564                                })
565                        {
566                            for (chip_no, hashrate) in t.iter().enumerate() {
567                                if let Some(chip_data) = hashboard.chips.get_mut(chip_no) {
568                                    chip_data.position = chip_no as u16;
569                                    chip_data.working = Some(true);
570                                    chip_data.hashrate = Some(hashrate.clone());
571                                }
572                            }
573                        };
574                    })
575                })
576            });
577
578        hashboards
579    }
580}
581
582impl GetHashrate for PowerPlay {
583    fn parse_hashrate(&self, data: &HashMap<DataField, Value>) -> Option<HashRate> {
584        let mut total_hashrate: f64 = 0.0;
585
586        data.get(&DataField::Hashrate).and_then(|v| {
587            v.as_array().map(|boards| {
588                boards.iter().for_each(|board| {
589                    if let Some(_idx) = board.get("Index").and_then(|v| v.as_u64()) {
590                        // Hashrate
591                        if let Some(h) = board
592                            .get("Hashrate")
593                            .and_then(|v| v.as_array())
594                            .and_then(|v| v.first().and_then(|f| f.as_f64()))
595                        {
596                            total_hashrate += h;
597                        };
598                    }
599                })
600            })
601        });
602
603        Some(HashRate {
604            value: total_hashrate,
605            unit: HashRateUnit::MegaHash,
606            algo: String::from("SHA256"),
607        })
608    }
609}
610
611impl GetExpectedHashrate for PowerPlay {
612    fn parse_expected_hashrate(&self, data: &HashMap<DataField, Value>) -> Option<HashRate> {
613        data.extract_map::<f64, _>(DataField::ExpectedHashrate, |f| HashRate {
614            value: f,
615            unit: HashRateUnit::TeraHash,
616            algo: String::from("SHA256"),
617        })
618    }
619}
620
621impl GetFans for PowerPlay {
622    fn parse_fans(&self, data: &HashMap<DataField, Value>) -> Vec<FanData> {
623        let mut fans: Vec<FanData> = Vec::new();
624
625        if let Some(fans_data) = data.get(&DataField::Fans)
626            && let Some(obj) = fans_data.as_object()
627        {
628            for (key, value) in obj {
629                if let Some(num) = value.as_f64() {
630                    // Extract the number from the key (e.g. "Fans Speed 3" -> 3)
631                    if let Some(pos_str) = key.strip_prefix("Fans Speed ")
632                        && let Ok(pos) = pos_str.parse::<i16>()
633                    {
634                        fans.push(FanData {
635                            position: pos,
636                            rpm: Some(AngularVelocity::from_rpm(num)),
637                        });
638                    }
639                }
640            }
641        }
642
643        fans
644    }
645}
646
647impl GetPsuFans for PowerPlay {}
648
649impl GetFluidTemperature for PowerPlay {}
650
651impl GetWattage for PowerPlay {
652    fn parse_wattage(&self, data: &HashMap<DataField, Value>) -> Option<Power> {
653        data.extract_map::<f64, _>(DataField::Wattage, Power::from_watts)
654    }
655}
656
657impl GetWattageLimit for PowerPlay {}
658
659impl GetLightFlashing for PowerPlay {
660    fn parse_light_flashing(&self, data: &HashMap<DataField, Value>) -> Option<bool> {
661        data.extract::<bool>(DataField::LightFlashing)
662    }
663}
664
665impl GetMessages for PowerPlay {}
666
667impl GetUptime for PowerPlay {
668    fn parse_uptime(&self, data: &HashMap<DataField, Value>) -> Option<Duration> {
669        data.extract::<u64>(DataField::Uptime)
670            .map(Duration::from_secs)
671    }
672}
673
674impl GetIsMining for PowerPlay {
675    fn parse_is_mining(&self, data: &HashMap<DataField, Value>) -> bool {
676        data.extract::<String>(DataField::IsMining)
677            .map(|state| state != "Idling")
678            .unwrap_or(false)
679    }
680}
681
682impl GetPools for PowerPlay {
683    fn parse_pools(&self, data: &HashMap<DataField, Value>) -> Vec<PoolData> {
684        let mut pools_vec: Vec<PoolData> = Vec::new();
685
686        if let Some(configs) = data
687            .get(&DataField::Pools)
688            .and_then(|v| v.pointer("/StratumConfigs"))
689            .and_then(|v| v.as_array())
690        {
691            for (idx, config) in configs.iter().enumerate() {
692                let url = config.get("pool").and_then(|v| v.as_str()).and_then(|s| {
693                    if s.is_empty() {
694                        None
695                    } else {
696                        Some(PoolURL::from(s.to_string()))
697                    }
698                });
699                let user = config
700                    .get("login")
701                    .and_then(|v| v.as_str())
702                    .map(String::from);
703                pools_vec.push(PoolData {
704                    position: Some(idx as u16),
705                    url,
706                    accepted_shares: None,
707                    rejected_shares: None,
708                    active: Some(false),
709                    alive: None,
710                    user,
711                });
712            }
713        }
714
715        if let Some(stratum) = data
716            .get(&DataField::Pools)
717            .and_then(|v| v.pointer("/Stratum"))
718            .and_then(|v| v.as_object())
719        {
720            for pool in pools_vec.iter_mut() {
721                if pool.position
722                    == stratum
723                        .get("Config Id")
724                        .and_then(|v| v.as_u64().map(|v| v as u16))
725                {
726                    pool.active = Some(true);
727                    pool.alive = stratum.get("IsPoolConnected").and_then(|v| v.as_bool());
728                    pool.user = stratum
729                        .get("Current User")
730                        .and_then(|v| v.as_str())
731                        .map(String::from);
732                    pool.url = stratum
733                        .get("Current Pool")
734                        .and_then(|v| v.as_str())
735                        .and_then(|s| {
736                            if s.is_empty() {
737                                None
738                            } else {
739                                Some(PoolURL::from(s.to_string()))
740                            }
741                        });
742
743                    // Get Stats
744                    if let Some(session) = data
745                        .get(&DataField::Pools)
746                        .and_then(|v| v.pointer("/Session"))
747                        .and_then(|v| v.as_object())
748                    {
749                        pool.accepted_shares = session.get("Accepted").and_then(|v| v.as_u64());
750                        pool.rejected_shares = session.get("Rejected").and_then(|v| v.as_u64());
751                    }
752                }
753            }
754        }
755
756        pools_vec
757    }
758}