Pool daemon: rejections by code in STATUS and /api/stats, the ledger's life and this run apart (7 October 2026, Devnet 3 pool-b: STATUS rejected=235,016 was the ledger's lifetime across the daemon's eras while the member had no rejected line in the hour; the two counters now say what they count)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 21:04:25 +00:00
parent 03dd76c40f
commit 96be28bc71
4 changed files with 26 additions and 7 deletions

View file

@ -87,7 +87,7 @@ pub fn stats(pool: &Pool) -> Value {
"scheme": "PPLNS", "fee_percent": pool.cfg.fee_percent, "min_payout_ign": pool.cfg.min_payout_ign, "pplns_window_blocks": pool.cfg.pplns_window_blocks,
"hashrate": round(s.hashrate(600, None, None), 0), "hashrate_unit": "H/s", "hashrate_1h": round(s.hashrate(3600, None, None), 0),
"miners": miners, "workers": workers,
"shares": { "accepted": s.accepted, "stale": s.stale, "rejected": s.rejected, "window_weight_blocks": round(s.pplns.total_weight, 4), "window_shares": s.pplns.shares.len() },
"shares": { "accepted": s.accepted, "stale": s.stale, "rejected": s.rejected, "window_weight_blocks": round(s.pplns.total_weight, 4), "window_shares": s.pplns.shares.len(), "rejected_by_code": s.rejected_by_code, "rejected_by_code_this_run": s.rejected_by_code_run, "counters_since": "ledger (rejected, stale, accepted, rejected_by_code are the ledger's life across restarts; rejected_by_code_this_run is this process)" },
"blocks_24h": f24, "blocks_confirmed_24h": c24, "blocks_orphaned_24h": o24,
"blocks_total": s.blocks_found, "blocks_confirmed_total": s.blocks_confirmed, "blocks_orphaned_total": s.blocks_orphaned,
"last_block": last.map(block_json),

View file

@ -261,8 +261,12 @@ async fn main() {
let (miners, workers) = pool.online();
let s = pool.state.lock().unwrap();
let (n, mean, p50, p99, max) = s.check_cost();
let by_code = |m: &std::collections::BTreeMap<String, u64>| {
let v: Vec<String> = m.iter().filter(|(c, n)| **n > 0 && c.as_str() != "stale").map(|(c, n)| format!("{c}={n}")).collect();
if v.is_empty() { "none".to_string() } else { v.join(",") }
};
println!(
"{} STATUS miners={miners} workers={workers} accepted={} stale={} rejected={} hashrate={:.0} H/s blocks={} confirmed={} orphans={} window={:.3} blocks/{} shares check_ms n={n} mean={mean:.3} p50={p50:.3} p99={p99:.3} max={max:.3}",
"{} STATUS miners={miners} workers={workers} accepted={} stale={} rejected={} hashrate={:.0} H/s blocks={} confirmed={} orphans={} window={:.3} blocks/{} shares check_ms n={n} mean={mean:.3} p50={p50:.3} p99={p99:.3} max={max:.3} rejected_by_code(ledger)={} rejected_by_code(this run)={}",
state::unix_ms(),
s.accepted,
s.stale,
@ -272,7 +276,9 @@ async fn main() {
s.blocks_confirmed,
s.blocks_orphaned,
s.pplns.total_weight,
s.pplns.shares.len()
s.pplns.shares.len(),
by_code(&s.rejected_by_code),
by_code(&s.rejected_by_code_run)
);
}
});

View file

@ -329,7 +329,7 @@ async fn handle_share(pool: &Arc<Pool>, m: &Arc<Member>, job_id: u64, nonce: u64
Err(code) => {
{
let mut s = pool.state.lock().unwrap();
s.refuse_share(&m.address, &m.worker, code == Code::Stale, None);
s.refuse_share(&m.address, &m.worker, code.as_str(), None);
}
{
let mut g = m.inner.lock().unwrap();
@ -352,7 +352,7 @@ async fn handle_share(pool: &Arc<Pool>, m: &Arc<Member>, job_id: u64, nonce: u64
if v.code != Code::Ok {
{
let mut s = pool.state.lock().unwrap();
s.refuse_share(&m.address, &m.worker, false, Some(v.cost_ms));
s.refuse_share(&m.address, &m.worker, v.code.as_str(), Some(v.cost_ms));
}
m.inner.lock().unwrap().rejected += 1;
m.send(Msg::ShareResult { job_id, nonce: hex_u64(nonce), accepted: false, code: v.code.as_str().into(), weight: 0.0, block: false }.line());

View file

@ -3,7 +3,7 @@
use crate::pplns::{Payee, Pplns, ShareRec};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::path::Path;
pub const WEI_PER_IGN: u128 = 1_000_000_000_000_000_000;
@ -110,6 +110,14 @@ pub struct State {
/// Q72: addresses already alerted for silence (cleared when they send again)
#[serde(default)]
pub alerted: HashMap<String, u64>,
/// Rejections by code over the ledger's life (persisted with it, as `rejected` is: 7 October 2026, Devnet 3
/// pool-b, the daemon's STATUS said rejected=235,016 while its member had not one rejected line in the hour;
/// the daemon's counter is the ledger's lifetime, the member's is its own run)
#[serde(default)]
pub rejected_by_code: BTreeMap<String, u64>,
/// Rejections by code since this process started (what a reader beside a member's own counters wants)
#[serde(skip)]
pub rejected_by_code_run: BTreeMap<String, u64>,
#[serde(skip)]
pub dirty: bool,
}
@ -136,6 +144,8 @@ impl State {
check_ms: VecDeque::new(),
history: HashMap::new(),
alerted: HashMap::new(),
rejected_by_code: BTreeMap::new(),
rejected_by_code_run: BTreeMap::new(),
dirty: false,
}
}
@ -245,8 +255,9 @@ impl State {
out
}
pub fn refuse_share(&mut self, address: &str, worker: &str, stale: bool, cost_ms: Option<f64>) {
pub fn refuse_share(&mut self, address: &str, worker: &str, code: &str, cost_ms: Option<f64>) {
let now = unix_ms();
let stale = code == "stale";
if let Some(c) = cost_ms {
self.check_ms.push_back(c);
}
@ -255,6 +266,8 @@ impl State {
} else {
self.rejected += 1;
}
*self.rejected_by_code.entry(code.to_string()).or_insert(0) += 1;
*self.rejected_by_code_run.entry(code.to_string()).or_insert(0) += 1;
let w = self.miner(address, worker, now);
if stale {
w.stale += 1