use crate::{
types::{ConnectionID, Difficulties, Difficulty, DifficultySettings, VarDiffBuffer},
utils, ConfigManager, SessionID,
};
use parking_lot::Mutex;
use std::sync::Arc;
use tracing::warn;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct Miner {
config_manager: ConfigManager,
shared: Arc<Shared>,
inner: Arc<Inner>,
}
#[derive(Debug)]
pub(crate) struct Inner {
pub(crate) worker_id: Uuid,
pub(crate) sid: SessionID,
pub(crate) connection_id: ConnectionID,
pub(crate) client: Option<String>,
pub(crate) name: Option<String>,
}
#[derive(Debug)]
pub(crate) struct Shared {
difficulties: Mutex<Difficulties>,
needs_ban: Mutex<bool>,
stats: Mutex<MinerStats>,
var_diff_stats: Mutex<VarDiffStats>,
difficulty_settings: Mutex<DifficultySettings>,
}
impl Miner {
#[must_use]
pub fn new(
connection_id: ConnectionID,
worker_id: Uuid,
sid: SessionID,
client: Option<String>,
name: Option<String>,
config_manager: ConfigManager,
difficulty: DifficultySettings,
) -> Self {
let now = utils::now();
let shared = Shared {
difficulties: Mutex::new(Difficulties::new_only_current(difficulty.default)),
needs_ban: Mutex::new(false),
stats: Mutex::new(MinerStats {
accepted: 0,
stale: 0,
rejected: 0,
last_active: now,
}),
var_diff_stats: Mutex::new(VarDiffStats {
last_timestamp: now,
last_retarget: config_manager
.difficulty_config()
.initial_retarget_time(now),
vardiff_buf: VarDiffBuffer::new(),
last_retarget_share: 0,
}),
difficulty_settings: Mutex::new(difficulty),
};
let inner = Inner {
worker_id,
sid,
connection_id,
client,
name,
};
Miner {
config_manager,
shared: Arc::new(shared),
inner: Arc::new(inner),
}
}
pub(crate) fn ban(&self) {
*self.shared.needs_ban.lock() = true;
}
pub fn consider_ban(&self) {
let stats = self.shared.stats.lock();
let total = stats.accepted + stats.stale + stats.rejected;
let config = &self.config_manager.current_config().connection;
if total >= config.check_threshold {
let percent_bad: f64 = ((stats.stale + stats.rejected) as f64 / total as f64) * 100.0;
if percent_bad < config.invalid_percent {
} else {
warn!(
id = ?self.inner.connection_id,
worker_id = ?self.inner.worker_id,
worker = ?self.inner.name,
client = ?self.inner.client,
"Miner banned. {} out of the last {} shares were invalid",
stats.stale + stats.rejected,
total
);
self.ban();
}
}
}
#[must_use]
pub fn difficulties(&self) -> Difficulties {
self.shared.difficulties.lock().clone()
}
pub fn valid_share(&self) {
let mut stats = self.shared.stats.lock();
stats.accepted += 1;
stats.last_active = utils::now();
drop(stats);
self.consider_ban();
self.retarget();
}
pub fn stale_share(&self) {
let mut stats = self.shared.stats.lock();
stats.stale += 1;
stats.last_active = utils::now();
drop(stats);
self.consider_ban();
}
pub fn rejected_share(&self) {
let mut stats = self.shared.stats.lock();
stats.rejected += 1;
stats.last_active = utils::now();
drop(stats);
self.consider_ban();
}
fn retarget(&self) {
let now = utils::now();
let mut difficulties = self.shared.difficulties.lock();
let mut var_diff_stats = self.shared.var_diff_stats.lock();
let stats = self.shared.stats.lock();
let since_last = now - var_diff_stats.last_timestamp;
var_diff_stats.vardiff_buf.append(since_last);
var_diff_stats.last_timestamp = now;
if !(((stats.accepted - var_diff_stats.last_retarget_share)
>= self
.config_manager
.difficulty_config()
.retarget_share_amount)
|| (now - var_diff_stats.last_retarget)
>= self.config_manager.difficulty_config().retarget_time as u128)
{
return;
}
var_diff_stats.last_retarget = now;
var_diff_stats.last_retarget_share = stats.accepted;
let avg = var_diff_stats.vardiff_buf.avg();
if avg <= 0.0 {
return;
}
let mut new_diff;
let target_time = self.config_manager.difficulty_config().target_time as f64 * 1000.0;
if avg > target_time {
if (avg / self.config_manager.difficulty_config().target_time as f64) <= 1.5 {
return;
}
new_diff = difficulties.current().as_u64() / 2;
} else if (avg / target_time) >= 0.7 {
return;
} else {
new_diff = difficulties.current().as_u64() * 2;
}
new_diff = new_diff.clamp(
self.shared.difficulty_settings.lock().minimum.as_u64(),
self.config_manager.difficulty_config().maximum_difficulty,
);
if new_diff != difficulties.current().as_u64() {
difficulties.update_next(Difficulty::from(new_diff));
var_diff_stats.vardiff_buf.reset();
}
}
#[must_use]
pub fn update_difficulty(&self) -> Option<Difficulty> {
let mut difficulties = self.shared.difficulties.lock();
difficulties.shift()
}
pub fn set_difficulty(&self, difficulty: Difficulty) {
let mut difficulties = self.shared.difficulties.lock();
difficulties.set_and_shift(difficulty);
}
#[must_use]
pub fn connection_id(&self) -> ConnectionID {
self.inner.connection_id.clone()
}
#[must_use]
pub fn worker_id(&self) -> Uuid {
self.inner.worker_id
}
#[must_use]
pub fn session_id(&self) -> SessionID {
self.inner.sid
}
}
#[derive(Debug, Clone)]
pub struct MinerStats {
accepted: u64,
stale: u64,
rejected: u64,
last_active: u128,
}
#[derive(Debug)]
pub struct VarDiffStats {
last_timestamp: u128,
last_retarget_share: u64,
last_retarget: u128,
vardiff_buf: VarDiffBuffer,
}
#[cfg(test)]
mod test {
use std::thread::sleep;
use super::*;
use crate::Config;
#[test]
fn test_valid_share() {
let connection_id = ConnectionID::new();
let worker_id = Uuid::new_v4();
let session_id = SessionID::from(1);
let config = Config::default();
let config_manager = ConfigManager::new(config.clone());
let diff_settings = DifficultySettings {
default: Difficulty::from(config.difficulty.initial_difficulty),
minimum: Difficulty::from(config.difficulty.minimum_difficulty),
};
let miner = Miner::new(
connection_id,
worker_id,
session_id,
None,
None,
config_manager,
diff_settings,
);
miner.valid_share();
for _ in 0..100 {
miner.valid_share();
sleep(std::time::Duration::from_millis(50));
}
let new_diff = miner.update_difficulty();
assert!(new_diff.is_some());
for _ in 0..100 {
miner.valid_share();
}
let new_diff = miner.update_difficulty();
assert!(new_diff.is_some());
}
}