igneum/pool/src/pool.rs

157 lines
5.8 KiB
Rust

//! The live pool: configuration, the share verifier engine, the ledger, the connected members and their jobs.
use crate::config::Config;
use crate::open::Open;
use crate::state::State;
use crate::vardiff::Vardiff;
use crate::verify::JobKey;
use kaspa_consensus_core::finality::KeyReveal;
use kaspa_grpc_client::GrpcClient;
use kaspa_hashes::Hash;
use kaspa_pow::igneum::{EpochRef, EpochSeeds, IgneumEngine};
use kaspa_rpc_core::RpcRawBlock;
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
/// A job issued to one member: the template it was built on and what its shares are checked against.
pub struct JobRec {
pub job_id: u64,
pub template_id: u64,
pub raw: RpcRawBlock,
pub key: JobKey,
pub epoch: Arc<EpochRef>,
pub shift: u32,
pub issued: Instant,
/// Set when a job on a newer tip was issued: shares after the grace are stale
pub superseded_at: Option<Instant>,
pub seen: HashSet<u64>,
pub daa_score: u64,
pub blue_score: u64,
/// The open pool: the share-chain parent and the split this template commits to (`open.rs`)
pub open_parent: Option<[u8; 32]>,
pub open_share_target64: u64,
}
pub struct MemberInner {
pub vardiff: Vardiff,
pub jobs: VecDeque<JobRec>,
pub last_parents: Option<Vec<Hash>>,
pub last_seeds: Option<EpochSeeds>,
pub accepted: u64,
pub stale: u64,
pub rejected: u64,
pub blocks: u64,
pub refused: u64,
pub last_share: Option<Instant>,
pub revealed: bool,
/// The member's own report (spec `stats`): hashrate, workers, and the proving flag (consequence C6)
pub reported_hashrate: f64,
pub reported_workers: u32,
pub proving: bool,
pub current_target64: u64,
pub in_flight_checks: u32,
/// The node's last template for this member with its epoch info (the open pool re-stamps it on a new parent)
pub last_template: Option<(RpcRawBlock, kaspa_rpc_core::RpcPowEpochInfo)>,
}
pub struct Member {
pub id: u64,
pub address: String,
pub worker: String,
pub key_hash: Hash,
pub reveal: KeyReveal,
pub remote: String,
pub connected_at: Instant,
pub tx: tokio::sync::mpsc::UnboundedSender<String>,
pub inner: Mutex<MemberInner>,
}
impl Member {
pub fn send(&self, line: String) {
let _ = self.tx.send(line);
}
pub fn job(&self, job_id: u64) -> Option<(JobKey, Arc<EpochRef>, u32, bool, u64, u64)> {
let g = self.inner.lock().unwrap();
g.jobs.iter().find(|j| j.job_id == job_id).map(|j| (j.key.clone(), j.epoch.clone(), j.shift, j.superseded_at.is_some(), j.daa_score, j.blue_score))
}
}
/// The network numbers the API reports beside the pool's own, refreshed every 5 s from the primary node.
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct NetInfo {
pub network: String,
pub chain_id: u64,
pub difficulty: f64,
pub hashrate: Option<f64>,
pub daa_score: u64,
pub block_count: u64,
pub blue_score: u64,
pub synced: bool,
pub node_version: String,
pub block_reward_ign: f64,
pub miner_reward_ign: f64,
pub updated_ms: u64,
pub epoch_seed: String,
pub epoch_index: u64,
/// Q73: the network's finality as the node reports it (`active`, `paused`, `window filling, N of M`, or
/// `unknown` when the node does not answer), the latest locked index and its age in seconds
pub finality: String,
pub finality_locked_index: u64,
pub finality_locked_age_s: u64,
}
pub struct Pool {
pub cfg: Config,
pub engine: Arc<IgneumEngine>,
pub state: Mutex<State>,
pub members: Mutex<HashMap<u64, Arc<Member>>>,
pub next_member_id: AtomicU64,
pub next_template_id: AtomicU64,
pub next_job_id: AtomicU64,
pub node: Arc<GrpcClient>,
/// A second connection to the same node for the confirmation walk and the network numbers (7 October 2026,
/// 06:01Z and 06:12:55Z: the walk's `get_block` calls shared the template connection and starved every template
/// request; the gRPC client is one request stream per connection)
pub walker: Arc<GrpcClient>,
pub extra_nodes: Vec<Arc<GrpcClient>>,
/// The pool's EVM coinbase address, lowercase 0x hex, named in every template's `IGNA` field
pub pool_address: [u8; 20],
pub pool_address_hex: String,
pub pay_address: kaspa_addresses::Address,
pub net: Mutex<NetInfo>,
pub want_templates: tokio::sync::Notify,
pub verify_permits: tokio::sync::Semaphore,
/// Template fetches in flight against the node (`--template-parallel`)
pub template_permits: tokio::sync::Semaphore,
pub started: Instant,
/// Template fetches that failed or timed out since start, and the unix ms of the last one that succeeded (0 =
/// never): a daemon whose node answers no template issues no job, and the STATUS line in the log says so
pub template_failures: AtomicU64,
pub last_template_ok_ms: AtomicU64,
/// TLS 1.3 on the member port (spec 9.3), when configured
pub tls: Option<crate::tls::Tls>,
/// The open pool's share chain and gossip, in `--open` mode
pub open: Option<Arc<Open>>,
}
impl Pool {
pub fn members_snapshot(&self) -> Vec<Arc<Member>> {
self.members.lock().unwrap().values().cloned().collect()
}
pub fn online(&self) -> (usize, usize) {
let m = self.members.lock().unwrap();
let addresses: HashSet<&str> = m.values().map(|x| x.address.as_str()).collect();
(addresses.len(), m.len())
}
pub fn now_s(&self) -> f64 {
self.started.elapsed().as_secs_f64()
}
pub fn template_id(&self) -> u64 {
self.next_template_id.fetch_add(1, Ordering::Relaxed) + 1
}
pub fn job_id(&self) -> u64 {
self.next_job_id.fetch_add(1, Ordering::Relaxed) + 1
}
}