Skip to content

Commit a00835a

Browse files
committed
send workers shares
1 parent e3bb07a commit a00835a

6 files changed

Lines changed: 298 additions & 1 deletion

File tree

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod proxy_state;
2525
mod router;
2626
mod share_accounter;
2727
mod shared;
28+
mod shares_monitor;
2829
mod translator;
2930

3031
const TRANSLATOR_BUFFER_SIZE: usize = 32;

src/shared/error.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1+
use std::fmt;
2+
13
pub enum Sv1IngressError {
24
TranslatorDropped,
35
DownstreamDropped,
46
TaskFailed,
57
}
8+
9+
#[derive(Debug)]
10+
pub enum Error {
11+
ReqwestError(reqwest::Error),
12+
}
13+
14+
impl fmt::Display for Error {
15+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
16+
match self {
17+
Error::ReqwestError(e) => write!(f, "ReqwestError: {}", e),
18+
}
19+
}
20+
}
21+
impl From<reqwest::Error> for Error {
22+
fn from(err: reqwest::Error) -> Self {
23+
Error::ReqwestError(err)
24+
}
25+
}

src/shares_monitor.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
use reqwest::Url;
2+
use roles_logic_sv2::utils::Mutex;
3+
use serde_json::json;
4+
use std::sync::Arc;
5+
use tracing::{debug, error};
6+
const BATCH_SIZE: u32 = 20; // Default batch size for sending shares
7+
8+
use crate::{
9+
config::Configuration,
10+
proxy_state::{DownstreamType, ProxyState},
11+
shared::error::Error,
12+
};
13+
14+
const STAGING_URL: &str = "http://staging-user-dashboard-server.dmnd.work/share/save";
15+
const LOCAL_URL: &str = "http://localhost:8787/api/share/save";
16+
const PRODUCTION_URL: &str = "http://user-dashboard-server.dmnd.work/share/save";
17+
18+
fn monitoring_server_url() -> String {
19+
// Determine the monitoring server URL based on the environment
20+
match Configuration::environment().as_str() {
21+
"staging" => STAGING_URL.to_string(),
22+
"local" => LOCAL_URL.to_string(),
23+
"production" => PRODUCTION_URL.to_string(),
24+
_ => unreachable!(),
25+
}
26+
}
27+
28+
#[derive(serde::Serialize, Clone, Debug)]
29+
pub struct ShareInfo {
30+
worker_name: String,
31+
difficulty: Option<f32>,
32+
job_id: i64,
33+
rejection_reason: Option<RejectionReason>, // if None, the share was accepted
34+
timestamp: u64,
35+
}
36+
37+
impl ShareInfo {
38+
pub fn new(
39+
worker_name: String,
40+
difficulty: Option<f32>,
41+
job_id: i64,
42+
rejection_reason: Option<RejectionReason>,
43+
) -> Self {
44+
ShareInfo {
45+
worker_name,
46+
difficulty,
47+
job_id,
48+
rejection_reason,
49+
timestamp: std::time::SystemTime::now()
50+
.duration_since(std::time::UNIX_EPOCH)
51+
.expect("Time went backwards")
52+
.as_secs(),
53+
}
54+
}
55+
}
56+
57+
#[derive(Debug, Clone)]
58+
pub struct SharesMonitor {
59+
pending_shares: Arc<Mutex<Vec<ShareInfo>>>,
60+
batch_size: u32,
61+
}
62+
63+
impl SharesMonitor {
64+
pub fn new() -> Self {
65+
SharesMonitor {
66+
pending_shares: Arc::new(Mutex::new(Vec::new())),
67+
batch_size: BATCH_SIZE,
68+
}
69+
}
70+
71+
/// Inserts a new share into the pending shares list.
72+
pub fn insert_share(&self, share: ShareInfo) {
73+
self.pending_shares
74+
.safe_lock(|event| {
75+
event.push(share);
76+
})
77+
.unwrap_or_else(|e| {
78+
error!("Failed to lock pending shares: {:?}", e);
79+
ProxyState::update_downstream_state(DownstreamType::TranslatorDownstream);
80+
});
81+
}
82+
83+
/// Retrieves the list of pending shares.
84+
fn get_pending_shares(&self) -> Vec<ShareInfo> {
85+
self.pending_shares
86+
.safe_lock(|event| event.clone())
87+
.unwrap_or_else(|e| {
88+
error!("Failed to lock pending shares: {:?}", e);
89+
ProxyState::update_downstream_state(DownstreamType::TranslatorDownstream);
90+
Vec::new()
91+
})
92+
}
93+
94+
/// Clears the list of pending shares.
95+
fn clear_pending_shares(&self) {
96+
self.pending_shares
97+
.safe_lock(|event| {
98+
event.clear();
99+
})
100+
.unwrap_or_else(|e| {
101+
error!("Failed to lock pending shares: {:?}", e);
102+
ProxyState::update_downstream_state(DownstreamType::TranslatorDownstream);
103+
});
104+
}
105+
106+
/// Monitors the pending shares and sends them to the monitoring server in batches.
107+
pub async fn monitor(&self) {
108+
let api = MonitorAPI::new(monitoring_server_url());
109+
let mut interval = tokio::time::interval(std::time::Duration::from_secs(60)); // Check every 60 seconds
110+
interval.tick().await; // Skip the first tick to avoid unnecessary error log
111+
loop {
112+
interval.tick().await;
113+
let shares_to_send = self.get_pending_shares();
114+
if !shares_to_send.is_empty() {
115+
if shares_to_send.len() >= self.batch_size as usize {
116+
match api.send_shares(shares_to_send.clone()).await {
117+
Ok(_) => {
118+
debug!("Successfully sent Shares: {:?} to API", &shares_to_send);
119+
}
120+
Err(err) => {
121+
error!("Failed to send shares: {}", err);
122+
}
123+
}
124+
self.clear_pending_shares(); // Clear after sending
125+
} else {
126+
debug!(
127+
"Current shares count ({}) is less than batch size ({}), waiting for more",
128+
shares_to_send.len(),
129+
self.batch_size
130+
);
131+
}
132+
} else {
133+
error!("No pending shares to send");
134+
}
135+
}
136+
}
137+
}
138+
139+
struct MonitorAPI {
140+
url: Url,
141+
client: reqwest::Client,
142+
}
143+
144+
impl MonitorAPI {
145+
fn new(url: String) -> Self {
146+
let client = reqwest::Client::new();
147+
MonitorAPI {
148+
url: url.parse().expect("Invalid URL"),
149+
client,
150+
}
151+
}
152+
153+
/// Sends a batch of shares to the monitoring server.
154+
async fn send_shares(&self, shares: Vec<ShareInfo>) -> Result<(), Error> {
155+
let token = crate::config::Configuration::token().expect("Token is not set");
156+
157+
debug!("Sending batch of {} shares to API", shares.len());
158+
let response = self
159+
.client
160+
.post(self.url.clone())
161+
.json(&json!({ "shares": shares, "token": token }))
162+
.send()
163+
.await?;
164+
165+
match response.error_for_status() {
166+
Ok(_) => Ok(()),
167+
Err(err) => {
168+
error!("Failed to send shares: {}", err);
169+
Err(err.into())
170+
}
171+
}
172+
}
173+
}
174+
175+
#[derive(Debug, Clone, serde::Serialize)]
176+
pub enum RejectionReason {
177+
JobIdNotFound,
178+
InvalidShare,
179+
InvalidJobIdFormat,
180+
DifficultyMismatch,
181+
}
182+
183+
impl std::fmt::Display for RejectionReason {
184+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185+
match self {
186+
RejectionReason::JobIdNotFound => write!(f, "Job ID not found"),
187+
RejectionReason::InvalidShare => write!(f, "Invalid share"),
188+
RejectionReason::InvalidJobIdFormat => write!(f, "Invalid job ID format"),
189+
RejectionReason::DifficultyMismatch => write!(f, "Difficulty mismatch"),
190+
}
191+
}
192+
}

src/translator/downstream/downstream.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{
22
api::stats::StatsSender,
33
proxy_state::{DownstreamType, ProxyState},
44
shared::utils::AbortOnDrop,
5+
shares_monitor::{RejectionReason, ShareInfo, SharesMonitor},
56
translator::{error::Error, utils::validate_share},
67
};
78

@@ -115,6 +116,7 @@ pub struct Downstream {
115116
pub(super) stats_sender: StatsSender,
116117
pub recent_jobs: RecentJobs,
117118
pub first_job: Notify<'static>,
119+
pub share_monitor: SharesMonitor,
118120
}
119121

120122
impl Downstream {
@@ -194,6 +196,7 @@ impl Downstream {
194196
stats_sender,
195197
recent_jobs: RecentJobs::new(),
196198
first_job: last_notify.expect("we have an assertion at the beginning of this function"),
199+
share_monitor: SharesMonitor::new(),
197200
}));
198201

199202
if let Err(e) = start_receive_downstream(
@@ -233,6 +236,31 @@ impl Downstream {
233236
error!("Failed to start notify task: {e}");
234237
ProxyState::update_downstream_state(DownstreamType::TranslatorDownstream);
235238
};
239+
240+
if let Err(e) = Self::start_share_monitor(task_manager.clone(), downstream.clone()).await {
241+
error!("Failed to start share monitor task: {e}");
242+
ProxyState::update_downstream_state(DownstreamType::TranslatorDownstream);
243+
}
244+
}
245+
246+
/// Starts the shares monitor task.
247+
async fn start_share_monitor(
248+
task_manager: Arc<Mutex<TaskManager>>,
249+
downstream: Arc<Mutex<Self>>,
250+
) -> Result<(), Error<'static>> {
251+
let (share_monitor, connection_id) =
252+
downstream.safe_lock(|s| (s.share_monitor.clone(), s.connection_id))?;
253+
254+
// Create an abortable task for the shares monitor
255+
let abortable = tokio::spawn(async move {
256+
info!("Starting shares monitor for downstream: {}", connection_id);
257+
share_monitor.clone().monitor().await;
258+
});
259+
260+
// Register the task with the task manager so it can be aborted when needed
261+
TaskManager::add_shares_monitor(task_manager, abortable.into())
262+
.await
263+
.map_err(|_| Error::TranslatorTaskManagerFailed)
236264
}
237265

238266
/// Accept connections from one or more SV1 Downstream roles (SV1 Mining Devices) and create a
@@ -353,6 +381,8 @@ impl Downstream {
353381
stats_sender: StatsSender,
354382
first_job: Notify<'static>,
355383
) -> Self {
384+
use crate::shares_monitor::SharesMonitor;
385+
356386
Downstream {
357387
connection_id,
358388
authorized_names,
@@ -368,6 +398,7 @@ impl Downstream {
368398
first_job,
369399
stats_sender,
370400
recent_jobs: RecentJobs::new(),
401+
share_monitor: SharesMonitor::new(),
371402
}
372403
}
373404
}
@@ -445,9 +476,19 @@ impl IsServer<'static> for Downstream {
445476
"Share rejected: can not convert v1 job id to number. v1 id: {}",
446477
request.job_id
447478
);
479+
480+
let share = ShareInfo::new(
481+
request.user_name.clone(),
482+
None,
483+
job_id_as_number.expect("checked above") as i64,
484+
Some(RejectionReason::InvalidJobIdFormat),
485+
);
486+
self.share_monitor.insert_share(share);
487+
448488
self.stats_sender.update_rejected_shares(self.connection_id);
449489
return false;
450490
}
491+
let job_id = job_id_as_number.clone().expect("checked above") as i64;
451492
crate::translator::utils::update_share_count(self.connection_id); // update share count
452493
if let Some(job) = self
453494
.recent_jobs
@@ -481,6 +522,23 @@ impl IsServer<'static> for Downstream {
481522
// Return false because submit was not properly handled
482523
return false;
483524
}
525+
// Share is accepted here
526+
let share = ShareInfo::new(
527+
request.user_name.clone(),
528+
Some(met_difficulty),
529+
job_id,
530+
None,
531+
);
532+
self.share_monitor.insert_share(share);
533+
} else {
534+
// met_difficulty is not latest difficulty, so we mark it as rejected
535+
let share = ShareInfo::new(
536+
request.user_name.clone(),
537+
None,
538+
job_id, // rejected because it was not sent upstream
539+
Some(RejectionReason::DifficultyMismatch),
540+
);
541+
self.share_monitor.insert_share(share);
484542
}
485543
}
486544
self.stats_sender.update_accepted_shares(self.connection_id);
@@ -490,11 +548,25 @@ impl IsServer<'static> for Downstream {
490548
);
491549
true
492550
} else {
551+
let share = ShareInfo::new(
552+
request.user_name.clone(),
553+
None,
554+
job_id,
555+
Some(RejectionReason::InvalidShare),
556+
);
557+
self.share_monitor.insert_share(share);
493558
error!("Share rejected: Invalid share");
494559
self.stats_sender.update_rejected_shares(self.connection_id);
495560
false
496561
}
497562
} else {
563+
let event = ShareInfo::new(
564+
request.user_name.clone(),
565+
None,
566+
job_id,
567+
Some(RejectionReason::JobIdNotFound),
568+
);
569+
self.share_monitor.insert_share(event);
498570
error!(
499571
"Share rejected: can not find job with id {}",
500572
request.job_id

src/translator/downstream/task_manager.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ enum Task {
1212
SendDownstream(AbortOnDrop),
1313
Notify(AbortOnDrop),
1414
Update(AbortOnDrop),
15+
SharesMonitor(AbortOnDrop),
1516
}
1617

1718
pub struct TaskManager {
@@ -86,4 +87,15 @@ impl TaskManager {
8687
.await
8788
.map_err(|_| ())
8889
}
90+
91+
pub async fn add_shares_monitor(
92+
self_: Arc<Mutex<Self>>,
93+
abortable: AbortOnDrop,
94+
) -> Result<(), ()> {
95+
let send_task = self_.safe_lock(|s| s.send_task.clone()).unwrap();
96+
send_task
97+
.send(Task::SharesMonitor(abortable))
98+
.await
99+
.map_err(|_| ())
100+
}
89101
}

0 commit comments

Comments
 (0)