mark job result as served and track serving metrics in miner service

keep completed jobs with served results for cleanup and log serving
info. add metric increments for result readiness and serving events.
re-verify nonce distance before serving and update job state
accordingly.
This commit is contained in:
rob thijssen
2025-09-14 14:05:26 +03:00
parent 6d9e5ab89b
commit e9eada661d
2 changed files with 57 additions and 7 deletions

View File

@@ -354,7 +354,7 @@ pub fn set_job_estimated_rate_backend(engine: &str, backend: &str, rate: f64) {
.set(rate);
}
pub fn set_job_estimated_rate(engine: &str, job_id: &str, rate: f64) {
pub fn set_job_estimated_rate(engine: &str, _job_id: &str, rate: f64) {
// Compatibility shim: map per-job series to backend="unknown" to avoid cardinality growth
JOB_ESTIMATED_RATE
.with_label_values(&[engine, "unknown"])
@@ -384,7 +384,7 @@ pub fn job_estimated_rate_backend(engine: &str, backend: &str, rate: f64) {
.set(rate);
}
pub fn job_estimated_rate(engine: &str, job_id: &str, rate: f64) {
pub fn job_estimated_rate(engine: &str, _job_id: &str, rate: f64) {
// Compatibility shim: prefer new API using backend; fallback to backend="unknown"
JOB_ESTIMATED_RATE
.with_label_values(&[engine, "unknown"])

View File

@@ -3,6 +3,7 @@
use crossbeam_channel::{bounded, Receiver, Sender};
use engine_cpu::{EngineCandidate, EngineRange, MinerEngine};
use pow_core::compat;
use primitive_types::U512;
use resonance_miner_api::*;
use std::collections::HashMap;
@@ -147,6 +148,21 @@ impl MiningService {
jobs.get(job_id).cloned()
}
pub async fn mark_job_result_served(&self, job_id: &str) {
let mut jobs = self.jobs.lock().await;
if let Some(job) = jobs.get_mut(job_id) {
if !job.result_served {
job.result_served = true;
log::info!(target: "miner", "Result served and marked for job: {}", job_id);
#[cfg(feature = "metrics")]
{
// reuse existing counter to trace served events
metrics::inc_mine_requests("result_served");
}
}
}
}
pub async fn remove_job(&self, job_id: &str) -> Option<MiningJob> {
let mut jobs = self.jobs.lock().await;
if let Some(mut job) = jobs.remove(job_id) {
@@ -188,8 +204,9 @@ impl MiningService {
);
}
// Retain jobs that are running or recently finished (e.g., within 5 minutes)
// Retain running jobs, completed-but-not-yet-served jobs, or anything recent (<5m)
let retain = job.status == JobStatus::Running
|| (job.status == JobStatus::Completed && !job.result_served)
|| job.start_time.elapsed().as_secs() < 300;
if !retain {
log::debug!(target: "miner", "Cleaning up old job {job_id}");
@@ -259,6 +276,7 @@ pub struct MiningJob {
pub thread_last_update: std::collections::HashMap<usize, std::time::Instant>,
pub thread_rate_ema: std::collections::HashMap<usize, f64>,
completed_threads: usize,
pub result_served: bool,
}
impl Clone for MiningJob {
@@ -284,6 +302,7 @@ impl Clone for MiningJob {
thread_last_update: self.thread_last_update.clone(),
thread_rate_ema: self.thread_rate_ema.clone(),
completed_threads: self.completed_threads,
result_served: self.result_served,
}
}
}
@@ -321,6 +340,7 @@ impl MiningJob {
thread_last_update: std::collections::HashMap::new(),
thread_rate_ema: std::collections::HashMap::new(),
completed_threads: 0,
result_served: false,
}
}
@@ -489,8 +509,16 @@ impl MiningJob {
result.distance,
result.nonce
);
self.best_result = Some(result);
self.best_result = Some(result.clone());
self.cancel_flag.store(true, Ordering::Relaxed);
// Result is now ready to be fetched via /result
log::info!(target: "miner", "Result ready: engine={}, nonce={}, distance={}",
self.engine_name, result.nonce, result.distance);
#[cfg(feature = "metrics")]
{
// reuse existing http metric bucket for visibility until dedicated counters exist
metrics::inc_mine_requests("result_ready");
}
}
}
}
@@ -821,7 +849,7 @@ pub async fn handle_result_request(
JobStatus::Cancelled => ApiResponseStatus::Cancelled,
};
let (nonce, work) = match &job.best_result {
let (nonce_hex, work_hex) = match &job.best_result {
Some(result) => (
Some(format!("{:x}", result.nonce)),
Some(hex::encode(result.work)),
@@ -829,14 +857,36 @@ pub async fn handle_result_request(
None => (None, None),
};
// Inline re-verify using the exact nonce bytes we will return
if let Some(result) = &job.best_result {
let nonce_be = result.nonce.to_big_endian();
let d2 = compat::get_nonce_distance(job.header_hash, nonce_be);
let ok = d2 <= job.distance_threshold;
log::info!(
target: "miner",
"Serving result: job_id={}, engine={}, ok={}, host_distance={}, threshold={}",
job_id,
job.engine_name,
ok,
d2,
job.distance_threshold
);
#[cfg(feature = "metrics")]
{
metrics::inc_mine_requests("result_served");
}
// Mark served to keep job until at least one fetch succeeds
state.mark_job_result_served(&job_id).await;
}
let elapsed_time = job.start_time.elapsed().as_secs_f64();
Ok(warp::reply::with_status(
warp::reply::json(&resonance_miner_api::MiningResult {
status,
job_id,
nonce,
work,
nonce: nonce_hex,
work: work_hex,
hash_count: job.total_hash_count,
elapsed_time,
}),