Pool on the 2.0.2 release branch: pool/ and docs/plans/pool.md from the pool line's tip pool-2.0 e063fdcd (pool-review-b 786e09cc: review B F12 durable payment intents, F13 admission bounds, INT-04 the verified ledger, INT-15 the challenge-bound authorize; merged by the pool seat on 8ff7a0f4 the class v5 day-state source via --exec-rpc and 8106ba27 the base unit from the node's network, the two fixes the devnet-4 pair needs; suite 51 of 51 on build-2 against successor-2.0.1 2b1a247a). The spec stays the hand-merged copy this branch carries. Rule 24's crate gate from this tree on build-6 against the pinned node 7cfa422a is the read-back below
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
552683e034
commit
0dfb3b05d3
3 changed files with 74 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
|
||||
|
|
@ -103,6 +105,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"),
|
||||
|
|
@ -155,6 +158,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()),
|
||||
|
|
@ -224,6 +228,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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
//! ```
|
||||
|
||||
mod admission;
|
||||
mod state_provider;
|
||||
mod api;
|
||||
mod config;
|
||||
mod frame;
|
||||
|
|
@ -122,6 +123,17 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
let node = clients.remove(0);
|
||||
// Igneum (O-2.6): the node's base unit in this process from its network (the dag info's network id), before any
|
||||
// amount is read: at 18 decimals a coinbase amount carries a high word that the 8-decimal reading refuses as
|
||||
// malformed (the devnet-4 pair, 8 October 2026: every template refused with "amount high word 1 ... at 8 decimals")
|
||||
match tokio::time::timeout(Duration::from_secs(10), node.get_block_dag_info()).await {
|
||||
Ok(Ok(info)) => {
|
||||
let params = kaspa_consensus_core::config::params::Params::from(info.network);
|
||||
kaspa_consensus_core::unit::install_base_unit(params.base_unit_decimals);
|
||||
eprintln!("base unit: {} decimals (network {})", params.base_unit_decimals, info.network);
|
||||
}
|
||||
other => eprintln!("base unit: the node's dag info did not answer ({other:?}); amounts read at the default unit"),
|
||||
}
|
||||
let walker = match tokio::time::timeout(Duration::from_secs(10), connect_reconnecting(cfg.nodes[0].clone())).await {
|
||||
Ok(Ok(c)) => Arc::new(c),
|
||||
Ok(Err(e)) => {
|
||||
|
|
@ -174,6 +186,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