Pool: the class v5 day state from the node's exec RPC (state_provider.rs, --exec-rpc defaulting to --evm-rpc), installed before the engine's first epoch; without it the daemon issued no job on devnet-4 (class v5 needs the execution state)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a4c9958cc8
commit
c24d508083
3 changed files with 63 additions and 0 deletions
|
|
@ -8,6 +8,8 @@ pub struct Config {
|
|||
pub nodes: Vec<String>,
|
||||
/// The first node's EVM JSON-RPC (payouts, chain id)
|
||||
pub evm_rpc: String,
|
||||
/// The node's exec RPC for the class v5 day state (`igneum_getPowStateLeaves`); empty means `--evm-rpc`
|
||||
pub exec_rpc: String,
|
||||
/// Where members connect (spec 9.3: 4461 mainnet, 4462 testnet, 4463 devnet)
|
||||
pub listen: String,
|
||||
/// The stats API and the web page
|
||||
|
|
@ -91,6 +93,7 @@ impl Config {
|
|||
let mut c = Config {
|
||||
nodes: Vec::new(),
|
||||
evm_rpc: String::new(),
|
||||
exec_rpc: String::new(),
|
||||
listen: String::new(),
|
||||
http: "127.0.0.1:4480".into(),
|
||||
data_dir: PathBuf::from("data"),
|
||||
|
|
@ -139,6 +142,7 @@ impl Config {
|
|||
match a {
|
||||
"--node" => c.nodes.push(val()),
|
||||
"--evm-rpc" => c.evm_rpc = val(),
|
||||
"--exec-rpc" => c.exec_rpc = val(),
|
||||
"--listen" => c.listen = val(),
|
||||
"--http" => c.http = val(),
|
||||
"--data-dir" => c.data_dir = PathBuf::from(val()),
|
||||
|
|
@ -197,6 +201,9 @@ impl Config {
|
|||
if c.evm_rpc.is_empty() {
|
||||
c.evm_rpc = format!("http://127.0.0.1:{evm_port}");
|
||||
}
|
||||
if c.exec_rpc.is_empty() {
|
||||
c.exec_rpc = c.evm_rpc.clone();
|
||||
}
|
||||
if c.payout_key.as_os_str().is_empty() {
|
||||
c.payout_key = c.data_dir.join("payout-key.json");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
//! --data-dir /var/lib/igneum-pool --fee-percent 1 --min-payout 1 [--dry-run]
|
||||
//! ```
|
||||
|
||||
mod state_provider;
|
||||
mod api;
|
||||
mod config;
|
||||
mod node;
|
||||
|
|
@ -171,6 +172,9 @@ async fn main() {
|
|||
Address::new(prefix, Version::PubKey, &h.finalize().as_bytes())
|
||||
};
|
||||
let state_path = cfg.data_dir.join("state.json");
|
||||
// class v5: the engine's day state comes from the node's exec RPC (the pool's own provider), installed before the first epoch
|
||||
kaspa_pow::igneum::install_day_state_provider(Arc::new(crate::state_provider::RpcStateProvider::new(&cfg.exec_rpc)));
|
||||
eprintln!("class v5 day state: {} ({})", cfg.exec_rpc, "igneum_getPowStateLeaves");
|
||||
let engine = Arc::new(IgneumEngine::new());
|
||||
let walker_for_open = walker.clone();
|
||||
let pool = Arc::new(Pool {
|
||||
|
|
|
|||
52
pool/src/state_provider.rs
Normal file
52
pool/src/state_provider.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//! The class v5 day state for the pool's own engine (8 October 2026, the devnet-4 pair): the pool verifies shares and
|
||||
//! builds its share verifier's epoch with `IgneumEngine`, and under class v5 every epoch's dataset is keyed by the chain's
|
||||
//! execution state after the epoch's seed block (spec 01 section 1.8.6). The engine asks the installed
|
||||
//! `DayStateProvider`; this one fetches the stream from the node's exec RPC (`igneum_getPowStateLeaves [blockHash]`)
|
||||
//! over a plain HTTP/1.1 POST on a blocking socket, once per epoch, as the miner's `RpcStateProvider` does
|
||||
//! (`igneum/miner/src/main.rs`). `--exec-rpc` names the URL; its default is the pool's `--evm-rpc` (the same node port).
|
||||
|
||||
use kaspa_hashes::Hash;
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct RpcStateProvider {
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl RpcStateProvider {
|
||||
pub fn new(url: &str) -> Self {
|
||||
Self { url: url.to_string() }
|
||||
}
|
||||
|
||||
fn post(&self, body: &str) -> Result<String, String> {
|
||||
let rest = self.url.trim_start_matches("http://");
|
||||
let (hostport, _) = rest.split_once('/').unwrap_or((rest, ""));
|
||||
let mut stream = std::net::TcpStream::connect(hostport).map_err(|e| format!("connect {hostport}: {e} (the node's exec RPC; --exec-rpc http://<host>:<port> names another)"))?;
|
||||
stream.set_read_timeout(Some(Duration::from_secs(20))).ok();
|
||||
let req = format!("POST / HTTP/1.1\r\nHost: {hostport}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len());
|
||||
stream.write_all(req.as_bytes()).map_err(|e| format!("write {hostport}: {e}"))?;
|
||||
let mut resp = Vec::new();
|
||||
stream.read_to_end(&mut resp).map_err(|e| format!("read {hostport}: {e}"))?;
|
||||
let text = String::from_utf8_lossy(&resp);
|
||||
let (_, body) = text.split_once("\r\n\r\n").ok_or_else(|| "no HTTP body".to_string())?;
|
||||
Ok(body.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl kaspa_pow::igneum::DayStateProvider for RpcStateProvider {
|
||||
fn state_stream(&self, block: Hash) -> Result<Vec<u8>, String> {
|
||||
let body = format!("{{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"igneum_getPowStateLeaves\",\"params\":[\"{block}\"]}}");
|
||||
let resp = self.post(&body)?;
|
||||
const KEY: &str = "\"streamHex\":\"0x";
|
||||
let hex_at = resp.find(KEY).ok_or_else(|| {
|
||||
let err = resp.find("\"message\":\"").map(|i| resp[i + 11..].split('"').next().unwrap_or("").to_string()).unwrap_or_else(|| resp.chars().take(200).collect());
|
||||
format!("exec RPC {} gave no stream for block {block}: {err}", self.url)
|
||||
})?;
|
||||
let rest = &resp[hex_at + KEY.len()..];
|
||||
let hex_str = rest.split('"').next().unwrap_or("");
|
||||
kaspa_pow::igneum::unhex_bytes(hex_str).ok_or_else(|| "malformed streamHex".to_string())
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
"the node's exec RPC (the pool's provider)"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue