use async_std::sync::{Arc, Mutex};
use chrono::{NaiveDateTime, Utc};
use extended_primitives::Buffer;
use log::warn;
use uuid::Uuid;
use crate::connection::MinerOptions;
#[derive(Debug, Clone)]
pub struct Miner {
pub id: Uuid,
pub sid: Buffer,
pub client: Option<String>,
pub name: Option<String>,
pub difficulty: Arc<Mutex<u64>>,
pub next_difficulty: Arc<Mutex<Option<u64>>>,
pub stats: Arc<Mutex<MinerStats>>,
pub job_stats: Arc<Mutex<JobStats>>,
pub options: Arc<MinerOptions>,
pub needs_ban: Arc<Mutex<bool>>,
}
impl Miner {
pub fn new(
id: Uuid,
client: Option<String>,
name: Option<String>,
sid: Buffer,
options: Arc<MinerOptions>,
difficulty: u64,
) -> Self {
Miner {
id,
sid,
client,
name,
difficulty: Arc::new(Mutex::new(difficulty)),
next_difficulty: Arc::new(Mutex::new(None)),
stats: Arc::new(Mutex::new(MinerStats {
accepted_shares: 0,
rejected_shares: 0,
last_active: Utc::now().naive_utc(),
})),
job_stats: Arc::new(Mutex::new(JobStats {
last_timestamp: Utc::now().naive_utc().timestamp(),
last_retarget: Utc::now().naive_utc().timestamp()
- options.retarget_time as i64 / 2,
times: Vec::new(),
current_difficulty: difficulty,
})),
options,
needs_ban: Arc::new(Mutex::new(false)),
}
}
pub async fn ban(&self) {
*self.needs_ban.lock().await = true;
}
pub async fn consider_ban(&self) {
let accepted = self.stats.lock().await.accepted_shares;
let rejected = self.stats.lock().await.rejected_shares;
let total = accepted + rejected;
let check_threshold = 500;
let invalid_percent = 50.0;
if total >= check_threshold {
let percent_bad: f64 = (rejected as f64 / total as f64) * 100.0;
if percent_bad < invalid_percent {
} else {
warn!(
"Miner: {} banned. {} out of the last {} shares were invalid",
self.id, rejected, total
);
}
}
}
pub async fn valid_share(&self) {
let mut stats = self.stats.lock().await;
stats.accepted_shares += 1;
stats.last_active = Utc::now().naive_utc();
drop(stats);
self.retarget().await;
}
pub async fn invalid_share(&self) {
self.stats.lock().await.rejected_shares += 1;
}
async fn retarget(&self) {
let now = Utc::now().naive_utc().timestamp();
let mut job_stats = self.job_stats.lock().await;
let since_last = now - job_stats.last_timestamp;
job_stats.times.push(since_last);
job_stats.last_timestamp = now;
if now - job_stats.last_retarget < self.options.retarget_time as i64 {
return;
}
let time_min = self.options.target_time as f64 * 0.40;
let time_max = self.options.target_time as f64 * 1.40;
job_stats.last_retarget = now;
let mut avg: i64 = job_stats.times.iter().sum::<i64>() / job_stats.times.len() as i64;
let mut new_difficulty = job_stats.current_difficulty.clone();
if (avg as f64) < time_min {
while (avg as f64) < time_min && new_difficulty < self.options.max_diff {
new_difficulty *= 2;
avg *= 2;
}
*self.next_difficulty.lock().await = Some(new_difficulty);
}
if (avg as f64) > time_max && new_difficulty >= self.options.min_diff * 2 {
while (avg as f64) > time_max && new_difficulty >= self.options.min_diff * 2 {
new_difficulty /= 2;
avg /= 2;
}
*self.next_difficulty.lock().await = Some(new_difficulty);
}
job_stats.times.clear();
}
pub async fn update_difficulty(&self) -> Option<u64> {
let next_difficulty = *self.next_difficulty.lock().await;
if let Some(next_difficulty) = next_difficulty {
*self.difficulty.lock().await = next_difficulty;
self.job_stats.lock().await.current_difficulty = next_difficulty;
*self.next_difficulty.lock().await = None;
Some(next_difficulty)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct MinerStats {
accepted_shares: u64,
rejected_shares: u64,
last_active: NaiveDateTime,
}
#[derive(Debug, Default)]
pub struct JobStats {
last_timestamp: i64,
last_retarget: i64,
times: Vec<i64>,
current_difficulty: u64,
}