Evidence: 4 October measurements (live hourly swap, first live lock and the 280 locks since, first GPU proof, one-click worker, generator v2, difficulty oscillation and v2 pending, 12-node propagation and partition, clock skew); rows 29 and 30 added; difficulty and floor claims softened

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-04 13:52:45 +00:00
parent 8d5a7c7948
commit 48ce378881
22 changed files with 1018 additions and 82 deletions

View file

@ -30,6 +30,7 @@ on:
- 'proto-opencl/**'
- 'proving/windows-wsl2/**'
- 'relay/clients/**'
- 'relay/playbooks/**'
- 'brand/icons/**'
- 'tools/ci/windows/**'
- '.github/workflows/windows.yml'
@ -68,7 +69,7 @@ jobs:
Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser -AllowClobber
}
Import-Module PSScriptAnalyzer
$folders = @('proto-cuda/windows-app', 'proto-cuda/windows-miner', 'proto-cuda/windows-node', 'proving/windows-wsl2', 'relay/clients', 'packaging/windows', 'tools/ci/windows')
$folders = @('proto-cuda/windows-app', 'proto-cuda/windows-miner', 'proto-cuda/windows-node', 'proving/windows-wsl2', 'relay/clients', 'relay/playbooks', 'packaging/windows', 'tools/ci/windows')
$total = 0
foreach ($f in $folders) {
$results = Invoke-ScriptAnalyzer -Path $f -Recurse -Severity Warning, Error -ExcludeRule PSAvoidUsingWriteHost, PSUseShouldProcessForStateChangingFunctions, PSUseSingularNouns, PSAvoidUsingPositionalParameters

View file

@ -7,9 +7,13 @@
//! igneum-ota-sign embedded prints the public key compiled into the app and its fingerprint
//! igneum-ota-sign fingerprint <public-key-file|hex>
//! igneum-ota-sign sha256 <file> the file's sha256 and size, for the manifest
//! igneum-ota-sign sign-jobs <private-key-file> <igneum-jobs.json> the remote-jobs file (src/jobs.rs), same key
//! igneum-ota-sign verify-jobs <public-key-file|hex> <igneum-jobs.json> <sig-file>
#[path = "../manifest.rs"]
mod manifest;
#[path = "../jobs.rs"]
mod jobs;
use ed25519_dalek::{Signer, SigningKey};
use std::path::Path;
@ -87,8 +91,32 @@ fn main() {
let size = std::fs::metadata(p).map(|m| m.len()).unwrap_or(0);
println!("{sum} {size}");
}
Some("sign-jobs") if args.len() == 3 => {
let seed = manifest::hex_decode(&read_key_arg(&args[1])).unwrap_or_else(|| die("private key is not hex"));
let seed: [u8; 32] = seed.try_into().unwrap_or_else(|_| die("private key is not 32 bytes"));
let sk = SigningKey::from_bytes(&seed);
let bytes = std::fs::read(&args[2]).unwrap_or_else(|e| die(&format!("{}: {e}", args[2])));
let text = std::str::from_utf8(&bytes).unwrap_or_else(|_| die("jobs file is not UTF-8"));
let f = jobs::parse(text).unwrap_or_else(|e| die(&format!("refusing to sign: {e}")));
eprintln!("signing {} job(s): {}", f.jobs.len(), f.jobs.iter().map(|j| format!("{} ({})", j.id, j.kind)).collect::<Vec<_>>().join(", "));
println!("{}", manifest::hex_encode(&sk.sign(&bytes).to_bytes()));
}
Some("verify-jobs") if args.len() == 4 => {
let pk = read_key_arg(&args[1]);
let bytes = std::fs::read(&args[2]).unwrap_or_else(|e| die(&format!("{}: {e}", args[2])));
let sig = std::fs::read_to_string(&args[3]).unwrap_or_else(|e| die(&format!("{}: {e}", args[3])));
match jobs::verify_and_parse(&bytes, sig.trim(), &pk) {
Ok(f) => {
println!("ok: {} job(s), published {}", f.jobs.len(), f.published_at);
for j in &f.jobs {
println!(" {} {} to {} on {} until {}{}: {}", j.id, j.kind, if j.target.all { "all".to_string() } else { j.target.machine_ids.join(",") }, j.target.platform, j.expires_at, if j.target.requires.is_empty() { String::new() } else { format!(" needs {}", j.target.requires.join(",")) }, j.label());
}
}
Err(e) => die(&e),
}
}
_ => {
eprintln!("usage: igneum-ota-sign keygen <priv> <pub> | sign <priv> <manifest.json> | verify <pub> <manifest.json> <sig> | embedded | fingerprint <pub> | sha256 <file>");
eprintln!("usage: igneum-ota-sign keygen <priv> <pub> | sign <priv> <manifest.json> | verify <pub> <manifest.json> <sig> | embedded | fingerprint <pub> | sha256 <file> | sign-jobs <priv> <jobs.json> | verify-jobs <pub> <jobs.json> <sig>");
std::process::exit(2);
}
}

View file

@ -48,6 +48,10 @@ pub struct Settings {
/// Over-the-air updates install by themselves at a safe moment (default on); off = download, then wait for Install now.
#[serde(default = "yes")]
pub auto_update: bool,
/// Signed remote jobs from Igneum (src/jobs.rs) run on this machine (default on for the devnet build; the
/// switch in Settings shows the signing key's fingerprint).
#[serde(default = "yes")]
pub remote_jobs: bool,
}
fn one() -> u32 {
@ -59,7 +63,7 @@ fn yes() -> bool {
impl Default for Settings {
fn default() -> Settings {
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true }
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true, remote_jobs: true }
}
}

View file

@ -40,6 +40,10 @@ pub enum Cmd {
OpenUpdateFile,
/// the over-the-air updater's threads report here (src/ota.rs)
Ota(crate::ota::Event),
/// the remote-job runner's threads report here (src/jobrun.rs)
Job(crate::jobrun::Event),
JobsAllow(bool),
JobsCheck,
WorkerBuilt(usize, Result<PathBuf, String>),
/// local minus the latest block's timestamp, seconds (from the node's EVM RPC)
ClockSample(f64),
@ -90,7 +94,7 @@ impl Shared {
st.mining.paused = settings.paused;
st.mining.accepted_total = settings.accepted_total;
st.address = address_state(&settings, &wallet_path);
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update };
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update, remote_jobs: settings.remote_jobs };
st.live_page = packaged.live_page.clone();
st.finality.message = "waiting for the miner".into();
st.clock.hint = crate::platform::clock_hint().into();
@ -356,6 +360,9 @@ pub struct Engine {
clock_https: Option<(f64, Instant)>,
clock_next_https: Instant,
clock_last_sample: Instant,
jobs: crate::jobrun::Jobs,
/// a remote job has the miners stopped; they restart when the runner lets go
job_hold: bool,
last_accepted: Option<Instant>,
telemetry: Option<Proc>,
telemetry_retry_at: Instant,
@ -394,6 +401,7 @@ impl Engine {
node_restart_at: None,
node_started_at: now,
node_starts: 0,
let jobs = crate::jobrun::Jobs::new(&shared);
node_restarts: 0,
node_log: None,
node_last_reading: None,
@ -423,6 +431,8 @@ impl Engine {
clock_https: None,
clock_next_https: now + Duration::from_secs(5),
clock_last_sample: now,
jobs,
job_hold: false,
last_accepted: None,
telemetry: None,
telemetry_retry_at: now,
@ -582,6 +592,17 @@ impl Engine {
m.worker = p;
m.needs_rebuild = false;
m.prepared = true;
Cmd::Job(ev) => {
if let Some(a) = self.jobs.event(&self.shared, ev) {
self.job_action(a);
}
}
Cmd::JobsAllow(on) => self.jobs.set_allowed(&self.shared, on),
Cmd::JobsCheck => {
if let Some(a) = self.jobs.check_now(&self.shared) {
self.job_action(a);
}
}
m.restart_at = Some(Instant::now());
}
Err(e) => {
@ -1311,6 +1332,7 @@ impl Engine {
if self.wrapper && now.duration_since(self.last_state_print) >= Duration::from_secs(3) {
self.last_state_print = now;
println!("STATE {}", self.shared.wrapper_state());
self.tick_jobs();
let _ = std::io::stdout().flush();
}
if now.duration_since(self.last_settings_save) >= Duration::from_secs(120) {
@ -1327,7 +1349,8 @@ impl Engine {
crate::ota::Ctx {
node_synced: st.node.synced && st.clock.severity != "block",
boundary_eta_s: if st.node.daa > 0 && st.program.boundary_daa > 0 { Some(st.program.eta_s) } else { None },
miner_busy: self.miners.iter().any(|m| m.building) || st.mining.cards.iter().any(|c| c.enabled && c.state == "starting"),
// a remote job in progress counts as busy: no update applies under it (src/jobrun.rs)
miner_busy: self.miners.iter().any(|m| m.building) || st.mining.cards.iter().any(|c| c.enabled && c.state == "starting") || self.jobs.active(),
daa: st.node.daa,
}
};
@ -1347,6 +1370,68 @@ impl Engine {
Ok(()) => {
{
let mut st = self.st();
/// The remote-job runner (src/jobrun.rs): polls and runs on its own threads; this tick starts queued jobs and
/// does what a job asks of the engine (miners stopped and held, a restart, the updater).
fn tick_jobs(&mut self) {
if self.quitting {
return;
}
if let Some(a) = self.jobs.tick(&self.shared) {
self.job_action(a);
}
if self.job_hold && !self.jobs.holds_miners() {
self.job_hold = false;
if self.running {
self.shared.event("info", "job finished; the miners restart");
for m in self.miners.iter_mut() {
m.restart_at = Some(Instant::now());
}
}
}
}
fn job_action(&mut self, a: crate::jobrun::Action) {
use crate::jobrun::Action;
match a {
Action::StopMiners(why) => {
self.job_hold = true;
self.stop_miners(&why);
for c in self.st().mining.cards.iter_mut().filter(|c| c.enabled) {
c.message = "stopped for a remote job".into();
}
self.jobs.miners_stopped(&self.shared);
}
Action::RestartMiners => {
self.stop_miners("remote job: restart miners");
for m in self.miners.iter_mut() {
m.restart_at = Some(Instant::now());
}
}
Action::RestartNode => {
if self.node_external {
self.shared.event("info", "remote job asked for a node restart, but the node is external; nothing done");
} else {
self.stop_miners("remote job: restart node");
self.stop_node();
self.node_restart_at = Some(Instant::now() + Duration::from_secs(3));
for m in self.miners.iter_mut() {
m.restart_at = Some(Instant::now());
}
self.st().node.message = "restart asked by a remote job".into();
}
}
Action::RestartApp => {
self.shared.event("info", "remote job: the app restarts (miners stop, then the node, then it opens again)");
self.quitting = true;
self.st().quitting = true;
}
Action::UpdateNow => {
self.shared.event("info", "remote job: update check now; a newer version installs at once");
self.ota.install_now(&self.shared);
}
}
}
st.update.applying = true;
st.update.status = "applying".into();
st.update.wait = String::new();
@ -1510,6 +1595,10 @@ impl Engine {
}
if self.miners[i].building {
continue;
if self.job_hold {
// a remote job has the GPU; the miners wait until it lets go (src/jobrun.rs)
continue;
}
}
if let Some(at) = self.miners[i].restart_at {
if now >= at {
@ -1979,6 +2068,7 @@ impl Engine {
}
self.stop_node();
if let Some(mut k) = self.keep_awake.take() {
self.jobs.abort(&self.shared, "the app is quitting");
k.stop();
}
let st = self.st();

View file

@ -96,7 +96,6 @@ struct Active {
pub struct Jobs {
url: String,
allowed: bool,
app_dir: PathBuf,
data_root: PathBuf,
dir: PathBuf,
ledger: Ledger,
@ -130,7 +129,6 @@ impl Jobs {
let j = Jobs {
url,
allowed,
app_dir,
data_root,
dir,
ledger,
@ -211,12 +209,12 @@ impl Jobs {
self.publish(shared);
}
pub fn check_now(&mut self, shared: &Arc<Shared>) {
pub fn check_now(&mut self, shared: &Arc<Shared>) -> Option<Action> {
if !self.allowed || self.busy {
return;
return None;
}
self.next_check = Instant::now();
self.tick(shared);
self.tick(shared)
}
/// Ends the running job (quit, or the switch turned off). The engine releases the miners itself.

View file

@ -22,6 +22,8 @@ mod state;
mod manifest;
mod ota;
mod update;
mod jobs;
mod jobrun;
use std::io::{BufRead, Write};
use std::sync::mpsc::channel;

View file

@ -263,3 +263,12 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
_ => Err("unknown api".into()),
}
}
"/api/jobs/allow" => {
let on = body.get("on").and_then(|v| v.as_bool()).ok_or("on missing")?;
shared.send(Cmd::JobsAllow(on));
Ok(json!({ "ok": true }))
}
"/api/jobs/check" => {
shared.send(Cmd::JobsCheck);
Ok(json!({ "ok": true }))
}

View file

@ -128,6 +128,46 @@ pub struct SettingsState {
pub vote: bool,
pub start_at_login: bool,
pub auto_update: bool,
/// signed remote jobs from Igneum run on this machine (src/jobs.rs)
pub remote_jobs: bool,
}
/// One remote job this machine ran (the ledger entry), for the Settings history and the last-job strip.
#[derive(Clone, Serialize, Default)]
pub struct JobHistory {
pub id: String,
pub kind: String,
pub title: String,
pub status: String, // running | done | failed | timeout | aborted
pub started_at: f64,
pub finished_at: f64,
pub exit: i64,
pub run_id: String,
pub uploaded: bool,
pub summary: String,
}
/// The remote-job runner (src/jobrun.rs): the switch, the running job, the last outcome, the history.
#[derive(Clone, Serialize, Default)]
pub struct JobsState {
pub allowed: bool,
pub key_fingerprint: String,
pub url_set: bool,
pub checked_at: f64,
pub error: String,
pub queued: u32,
pub active: bool,
pub id: String,
pub kind: String,
pub title: String,
pub stage: String,
pub message: String, // the last output line
pub started_at: f64,
pub run_id: String,
pub results: Vec<String>, // RESULT and STAGE lines so far
pub last: JobHistory,
pub last_results: Vec<String>,
pub history: Vec<JobHistory>,
}
#[derive(Clone, Serialize, Default)]
@ -197,6 +237,7 @@ pub struct State {
pub address: AddressState,
pub settings: SettingsState,
pub update: UpdateState,
pub jobs: JobsState,
pub events: Vec<Event>,
pub uptime_s: u64,
pub now: f64,

View file

@ -74,6 +74,15 @@ body.mac .top{padding-left:92px}
.banner.update.urgent{background:rgba(242,84,27,.55);border-bottom-color:var(--ember);color:#fff;font-weight:600}
.banner .prog{flex-basis:100%;height:3px;background:rgba(255,255,255,.12);border-radius:2px;overflow:hidden;margin-top:-2px}
.banner .prog i{display:block;height:100%;width:0;background:var(--ember);transition:width .5s linear}
/* remote job strip (src/jobrun.rs): running, then the outcome */
.banner.job{flex-wrap:wrap;row-gap:8px;background:rgba(255,179,92,.1);border-bottom-color:rgba(255,179,92,.4)}
.banner.job.failed{background:rgba(242,84,27,.14);border-bottom-color:rgba(242,84,27,.5)}
.banner.job .job-results{flex-basis:100%;margin:0;font-size:11px;line-height:1.5;color:var(--ink-2);white-space:pre-wrap;word-break:break-word;max-height:120px;overflow:auto}
.job-history table{margin-top:8px}
.job-history th{text-align:left;font-weight:500;color:var(--ash);font-size:11px;padding:4px 6px 4px 0}
.job-history td{padding:5px 6px 5px 0;border-top:1px solid var(--line);vertical-align:top}
.job-history tr.failed td,.job-history tr.timeout td,.job-history tr.aborted td{color:var(--ember)}
.job-history tr.running td{color:var(--molten)}
.clock-card{margin-top:12px;border:1px solid rgba(242,84,27,.5);background:rgba(242,84,27,.08);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px}
.clock-card.warn{border-color:rgba(255,179,92,.4);background:rgba(255,179,92,.06)}
.clock-msg{font-size:14px;color:var(--bone);line-height:1.45}

View file

@ -118,6 +118,7 @@
$('s-live').hidden = !state.live_page;
var u = state.update;
$('s-auto-update').checked = !!state.settings.auto_update;
fillJobsSettings(state.jobs || {});
$('s-install').hidden = !((u.ready || u.downloaded || u.status === 'error') && !u.applying);
$('s-update-note').textContent = settingsUpdateNote(u);
}
@ -145,6 +146,9 @@
$('update-install').addEventListener('click', function () { api('api/update/install', {}); toast('Installing at once'); });
$('update-open').addEventListener('click', function () { api('api/update/open', {}); });
$('update-later').addEventListener('click', function () { $('update-banner').hidden = true; $('update-banner').dataset.dismissed = (state && state.update) ? (state.update.status + ':' + state.update.version) : '1'; layoutBanners(); });
$('job-hide').addEventListener('click', function () { $('job-banner').hidden = true; $('job-banner').dataset.dismissed = jobKey(state && state.jobs); layoutBanners(); });
$('s-jobs-allow').addEventListener('change', function () { api('api/jobs/allow', { on: this.checked }); setTimeout(fillSettings, 800); });
$('s-jobs-check').addEventListener('click', function () { api('api/jobs/check', {}); $('s-jobs-note').textContent = 'Checking.'; setTimeout(fillSettings, 4000); });
// ---------- bottom bar ----------
$('btn-pause').addEventListener('click', function () {
@ -402,7 +406,7 @@
// fixed banners push the content down by their height
function layoutBanners() {
var h = 0;
['clock-banner', 'update-banner'].forEach(function (id) { var b = $(id); if (!b.hidden) { b.style.top = (60 + h) + 'px'; h += b.offsetHeight; } });
['clock-banner', 'update-banner', 'job-banner'].forEach(function (id) { var b = $(id); if (!b.hidden) { b.style.top = (60 + h) + 'px'; h += b.offsetHeight; } });
$('main').style.top = (60 + h) + 'px';
}
window.addEventListener('resize', layoutBanners);
@ -462,12 +466,57 @@
if (kind === 'updated') { u.status = 'current'; u.available = false; u.ready = false; u.downloaded = false; u.updated_from = '0.3.0'; u.version = '0.3.1'; }
return u;
}
// ---------- remote jobs (src/jobrun.rs): one strip while a job runs and after it, the settings block ----------
function jobKey(j) { if (!j) return ''; return j.active ? ('run:' + j.id) : (j.last && j.last.id ? ('done:' + j.last.id + ':' + j.last.status) : ''); }
function jobTitle(j) { return j.title && j.title !== j.kind ? j.title : (j.kind === 'shard-benchmark' ? 'shard benchmark' : j.kind); }
function jobLine(j, now) {
if (j.active) {
var m = Math.max(0, Math.floor((now - j.started_at) / 60));
return { text: 'Job: ' + jobTitle(j) + ' running, ' + m + ' min' + (j.stage ? '. ' + cap(j.stage) + '.' : '.') + (j.message ? ' ' + j.message : ''), results: j.results || [] };
}
var l = j.last;
if (!l || !l.id) return null;
// the last outcome stays up for 30 minutes
if (l.finished_at && now - l.finished_at > 1800) return null;
var d = Math.max(0, Math.round((l.finished_at - l.started_at) / 60));
return { text: 'Job: ' + (l.title || l.kind) + ' ' + l.status + ' after ' + d + ' min, exit ' + l.exit + (l.uploaded ? ', report uploaded' : ', report not uploaded') + (l.summary ? '. ' + l.summary : ''), results: j.last_results || [], failed: l.status !== 'done' };
}
function renderJobs(s) {
var j = s.jobs || {}, b = $('job-banner'), l = jobLine(j, s.now || 0);
var key = jobKey(j);
var show = !!l && b.dataset.dismissed !== key && phase === 'dashboard';
if (show) {
$('job-text').textContent = l.text;
b.classList.toggle('failed', !!l.failed);
var r = (l.results || []).filter(function (x) { return x.indexOf('RESULT') === 0; }).slice(-6);
$('job-results').textContent = r.join('\n');
$('job-results').hidden = !r.length;
}
if (b.hidden === show) { b.hidden = !show; layoutBanners(); }
else if (show) layoutBanners();
}
function fillJobsSettings(j) {
$('s-jobs-allow').checked = !!j.allowed;
$('s-jobs-key').textContent = j.key_fingerprint ? 'signing key sha256:' + j.key_fingerprint : '';
var parts = [];
if (!j.allowed) parts.push('Off: nothing runs here until it is switched on.');
else if (!j.url_set) parts.push('No jobs address in this build.');
else if (j.error) parts.push(j.error + '.');
else if (j.active) parts.push('Running ' + jobTitle(j) + ' (' + j.id + ').');
else parts.push('Nothing running' + (j.checked_at ? '; checked ' + rel((state.now || 0) - j.checked_at) : '') + (j.queued ? '; ' + j.queued + ' queued' : '') + '.');
$('s-jobs-note').textContent = parts.join(' ');
var h = j.history || [];
$('s-jobs-history').innerHTML = h.length ? '<table><thead><tr><th>job</th><th>kind</th><th>started</th><th>exit</th><th>report</th></tr></thead><tbody>' + h.map(function (r) {
return '<tr class="' + esc(r.status) + '"><td class="mono" title="' + esc(r.summary || '') + '">' + esc(r.id) + '</td><td>' + esc(r.kind) + '</td><td>' + (r.started_at ? clock(r.started_at) : '') + '</td><td>' + esc(r.status) + (r.status !== 'running' ? ' (' + r.exit + ')' : '') + '</td><td>' + (r.uploaded ? 'uploaded' : (r.status === 'running' ? 'pending' : 'not uploaded')) + '</td></tr>';
}).join('') + '</tbody></table>' : '<p class="note">No job has run on this machine yet.</p>';
}
function render(s) {
state = s; stateAt = performance.now();
if (forcedUpdate) { s.update = sampleUpdate(forcedUpdate); if (forcedUpdate === 'updated') s.version = '0.3.1'; }
renderPill(s);
renderClock(s);
renderUpdate(s);
renderJobs(s);
if (phase === 'cards') renderCards(s);
if (phase === 'dashboard') renderDashboard(s);
if (s.quitting && !$('btn-quit').disabled) { $('btn-quit').disabled = true; }

View file

@ -35,6 +35,11 @@
<button class="btn small ghost" id="update-later">Later</button>
<span class="prog" id="update-prog" hidden><i></i></span>
</div>
<div class="banner job" id="job-banner" hidden>
<span id="job-text"></span>
<button class="btn small ghost" id="job-hide">Hide</button>
<pre class="job-results mono" id="job-results" hidden></pre>
</div>
<main id="main">
@ -266,6 +271,14 @@
<label class="switch"><input type="checkbox" id="s-auto-update"><span class="track"></span><span>Install updates by itself at a safe moment</span></label>
<p class="note" id="s-update-note"></p>
</div>
<div class="field">
<div class="k">remote jobs</div>
<div class="row between"><label class="switch"><input type="checkbox" id="s-jobs-allow"><span class="track"></span><span>Allow remote jobs from Igneum (signed)</span></label><button class="btn small" id="s-jobs-check">Check now</button></div>
<p class="note">Igneum publishes signed jobs (a benchmark, a script, a file to fetch, logs to collect, a restart) next to the update manifest. This machine runs each one once and reports to the Igneum log intake. Only jobs signed by the key below run; nothing else can send one.</p>
<p class="note mono small" id="s-jobs-key"></p>
<p class="note" id="s-jobs-note"></p>
<div class="job-history" id="s-jobs-history"></div>
</div>
<div class="field">
<div class="k">folders</div>
<p class="note mono small" id="s-node-dir"></p>

View file

@ -1,6 +1,6 @@
# Igneum evidence: every public claim, with its status
3 October 2026. One row per claim the homepage (`site/index.html`) and the litepaper (`site/litepaper.html`) make. Every number here is copied from `docs/bench-log.md`, which names the machine, the date and the command; nothing is restated from memory. Where a bench-log figure is approximate, this table says so.
4 October 2026. One row per claim the homepage (`site/index.html`) and the litepaper (`site/litepaper.html`) make. Every number here is copied from `docs/bench-log.md`, which names the machine, the date and the command, or from the result files it names; nothing is restated from memory. Where a bench-log figure is approximate, this table says so.
## The five labels
@ -12,58 +12,74 @@
| reproduced externally | Somebody outside the project ran the published command on their own hardware and got the published result |
| reviewed independently | Somebody outside the project, paid or not, read the code or the rule and published their finding |
Three rules for reading the table:
Four rules for reading the table:
1. Nothing on this chain has been reproduced externally or reviewed independently. Every row's last column says "none yet". The repository is private until January 2027 (`site/journey.json`), so the first two labels are the ceiling today.
1. Nothing on this chain has been reproduced externally or reviewed independently. Every row's last column says "none yet". The repository is private until January 2027 (`site/journey.json`), so the first three labels are the ceiling today.
2. A status applies to the exact version in the row. An audit of one version never covers a newer one; when the version changes, the status falls back to "tested by the team" until the new version is reproduced or reviewed again.
3. "Tested by the team" on one machine is one machine. The rows say which. Discrete AMD, Intel and a 2019-class CPU core have not run anything.
4. The 12-node cloud network of 4 October 2026 (`infra/cloud-devnet`, Hetzner VMs in five locations) is the project's own. Rows that cite it are tested by the team, not reproduced externally.
Versions in the table: `igneum-pow` is the Rust crate at `igneum-pow/Cargo.toml` version 0.1.0. "Repo" commits are this repository's. "Fork" commits are `vendor/igneum-node` and its worktrees (`-r3`, `-diff`, `-exec`, `-harness`), which are not in this repository's history; the row names the fork commit by its message as the bench log does. The spec is `docs/spec/` version 0.1.
Versions in the table: `igneum-pow` is the Rust crate at `igneum-pow/Cargo.toml`, version 0.2.0 since 4 October 2026 (generator version 2; 0.1.0 rows are marked). "Repo" commits are this repository's. "Fork" commits are `vendor/igneum-node` and its worktrees (`-v4`, `-diff`, `-exec`, `-harness`, `-fin-fixes`), which are not in this repository's history; the row names the fork commit or branch as the bench log does. The live devnet is devnet v4 (genesis 10:05 BST, 4 October 2026, branch `devnet-v4`). The spec is `docs/spec/` version 0.1.
## The table
| # | Claim | Where it is made | Status | Version or commit | Reproducible test | Result, date, machine | Independent verification |
|---|---|---|---|---|---|---|---|
| 1 | A new mining program every hour, compiled by the miner, with no human in the loop | Homepage hero and "This hour's mining program"; litepaper Mining | tested by the team | igneum-pow 0.1.0; repo `9812466`; fork "PoW: header-bound lottery engine, real-hash miner modes, genesis bits 0x1e400000" | `igneum-miner mine --engine igneum-pow` against a 3-node `igneumd --devnet`, with `proto-metal/igneum-bench --serve` as the GPU worker; bench-log "first devnet blocks on the real lottery hash" | Epoch change crossed live at DAA 3,600: new seed, a 128-load program compiled by the Metal worker in 129 ms, 0 rejected blocks across the change. 3 October 2026, Apple M5 Max. The epoch seed on the devnet is the epoch block hash; the VDF of row 2 is not wired in yet | none yet |
| 2 | The program seed passes through a 10-minute verifiable delay from a certified checkpoint, so nobody can grind the seed | Litepaper Mining, vs RandomX ("Closed by a verifiable delay") | implemented | repo `792776e`; `proto-vdf/` | `proto-vdf` full 10-minute runs and the tamper cases in `proto-vdf/README.md`; bench-log "proto-vdf" | Class group 1024-bit: 163,000 squarings per second, 10-min eval 585.4 s, prove 9.1 s on 12 threads, verify 4.47 ms, 516-byte proof; wrong checkpoint, flipped seed bit and T+1 all rejected; grinding model gains 0 blocks per epoch with the delay against +3.62 at a 30% advantage without it. 3 October 2026, Apple M5 Max, one core. Prototype only: not in the node, not reviewed against chiavdf (O-4.1) | none yet |
| 3 | The dataset is memory-hard: computing an item costs more than loading it | Litepaper Mining and vs RandomX; homepage vs RandomX ("Memory 2 GB, growing") | tested by the team | repo `58a5a63`; igneum-pow 0.1.0 (`memhard.rs`); `proto-metal/MEMHARD.md` | `proto-metal/igneum-bench --inline-dataset` against the honest run at 1 GiB and 256 MiB; bench-log "memory-hard dataset" | Honest 45.2 Mhash/s, inline (never reads the dataset) 9.49 Mhash/s, ratio 0.21, at 1 GiB; 0.10 at 256 MiB. 3 October 2026, Apple M5 Max. Apple only: the shortcut ratio has not run on NVIDIA or AMD (O-1.5). Review round 3 priced a 256 MiB on-die cache chip at about 2.4x, approximate, which the measurement does not answer (ledger M16) | none yet |
| 4 | The same program produces identical hashes on three GPU vendors, cache and dataset included | Litepaper vs RandomX ("Bit-exact on Apple and NVIDIA, measured"), For miners; homepage | tested by the team | repo `f2e903e`, `0f1fdaf`; pack `proto-cuda/packs/igneum-genesis-mh`; igneum-pow 0.1.0 | The 96 test vectors of the pack through `proto-metal/igneum-bench`, `proto-cuda/host.cu`, `proto-opencl/host.c`; batch fingerprint at `--batch-log2 24`; bench-log entries "RTX 5090, memory-hard dataset", "AMD gfx1036", "RTX 5090 through NVIDIA OpenCL" | 96/96 on Apple Metal (M5 Max), NVIDIA CUDA and NVIDIA OpenCL (RTX 5090, Windows), AMD OpenCL (Ryzen 7 9800X3D integrated gfx1036, 1 compute unit), Apple OpenCL, pocl and two CPU references; batch fingerprint `98af644e993239e2` over 16.7 million nonces identical on the AMD chip and the 5090. 3 October 2026. The AMD device is an integrated chip; no discrete AMD card and no Intel card has run anything (O-1.15) | none yet |
| 5 | A CPU verifies one hash in under 10 ms by simulating one warp | Litepaper Mining ("about ten milliseconds"), vs RandomX; roadmap gate 2 | tested by the team | repo `75cac18`; igneum-pow 0.1.0 (`verify.rs`) | `cargo test` and the crate bench in `igneum-pow/`; bench-log "igneum-pow: Rust crate bit-exact with proto-metal" | 0.411 to 0.579 ms per 32-lane warp steady, 0.41 to 0.87 ms cold, average of 20, 1 GiB dataset, cache held, one M5 Max performance core; the Swift verifier 0.63 to 1.2 ms. Gate margin about 17x on this core. 3 October 2026. Not measured on a 2019-class laptop core (O-1.14) | none yet |
| 6 | The hash is bound to the header: one nonce serves one header, and a wrong nonce is rejected | Spec 1.6; litepaper Mining (implied by "checks a hash") | tested by the team | repo `33f7b33`, `9812466`; igneum-pow 0.1.0 (`bind.rs`, 8 bound vectors, 29 crate tests) | `igneum-miner bad-nonce` against a devnet node; `igneum-pow hash-bound` for the 96-nonce job across the 2^32 lane boundary; bench-log "first devnet blocks on the real lottery hash" | 833 blocks accepted by `igneum-lottery-v1-bound` on 3 nodes, 0 rejections; `bad-nonce` gave Reject(BlockInvalid); Metal, OpenCL and CUDA (emulated) workers bit-exact with the crate on the lane-boundary job. 3 October 2026, Apple M5 Max | none yet |
| 7 | The devnet runs at one block a second | Homepage stats ("1 / s"); litepaper Speed; roadmap phase 3 | tested by the team | repo `9812466`, `e9328c6`; fork worktree `vendor/igneum-node-diff` branch `difficulty` | 3-node CPU devnet, `igneum-miner mine --engine igneum-pow`, 660 s; the dual-lane rule's 3-node test network (ports 26800 to 26821); bench-log "first devnet blocks" and "difficulty controller" | CPU devnet: 1.29 blocks/s over 641 s, 1.03 blocks/s over blocks 610 to 816 after the first retarget, sink identical on 3 nodes at 61 of 64 samples. Kaspa's sampled rule on the overnight devnet did not hold the rate across a hashrate step (5.44 blocks/s for five minutes, 4.7x the schedule for eight minutes). The Igneum dual-lane rule's test network reached 1 block/s within 10% after 132 s, worst gap 7.3 s. 3 October 2026, Apple M5 Max. The phase 3 gate also asks for proofs under 60 s behind the tip; no proof exists (row 15) | none yet |
| 8 | Blocks are mined by GPUs on Apple and NVIDIA | Homepage live strip; journey phase 3 ("GPU miners on three vendors") | tested by the team | repo `9812466`, `e9328c6`; fork as row 1 | Metal worker `proto-metal/igneum-bench --serve` driven by `igneum-miner --worker`, 300 s; the overnight devnet record `sim/difficulty/devnet-2026-10-03.csv`; bench-log "first devnet blocks" and "difficulty controller" | Metal: 506 jobs, 5,636 blocks found and accepted, 0 rejected, 0 CPU/GPU mismatches, 28.2 MH/s wall. NVIDIA: the overnight devnet record shows the PC's RTX 5090 mining at 116 MH/s estimated from the blocks, and the finality follower counted eight RTX 5090 identities at 862 to 936 blocks each. 3 October 2026, Apple M5 Max and the Windows PC | none yet |
| 9 | Blocks are mined by a GPU on AMD | Journey phase 3 ("three vendors") | implemented | repo `0f1fdaf`; bound kernel `kernel_bound.cl` in the pack | `proto-opencl/host.c --serve` on an AMD device against a devnet node | The bound OpenCL kernel is bit-exact with the crate on Apple OpenCL and the memory-hard pack passes 96/96 on AMD gfx1036, but no block count mined by an AMD device is recorded in the bench log. Until one is, the three-vendor mining claim is two vendors mined plus one vendor verified | none yet |
| 10 | Checkpoints lock every 30 s of chain at two thirds of all 30-day weight (raised from 56.7% of total on 4 October 2026), and the floor stops conflicting locks in partitions and eclipses | Litepaper Finality, "What Igneum does not claim"; homepage "locked every 30 seconds" | tested by the team | repo `bbb264a` (simulation), `60b1412` (fork run); fork "Finality: BLS12-381 vote keys ..." through "Miner: BLS identity ..."; spec 3.10 | `sim/finality_v2.py` (results in `sim/results_v2.md`); the 4-miner test network `igneum-devnet-7` with `getFinalityCheckpoints` on three nodes; bench-log "finality rule V2" and "igneum-node devnet v2" | Simulation: 0 conflicting locks in every honest partition and eclipse scenario with the floor; without it both sides of a 50/50 split lock after 60 min. Test network: 72 of 72 determined checkpoints locked on all three nodes, lock latency median 0.80 s, p90 1.08 s, 0 conflicting certificates; an equivocating key stripped at index 43 and excluded; with one voter left (39.6% of total) 0 locks for 749 s, then the heal locked 13 checkpoints within 30 s. 3 October 2026, Apple M5 Max, 53 minutes. Not on the live devnet (its miners do not vote yet); the simulation has no DAG; one unexplained stall of all three nodes in the first run, not reproduced | none yet |
| 11 | Hashrate that arrived today has almost no vote: ten days of the whole network's hashrate to reach a third of the weight, twenty for two thirds; 51% never reaches two thirds while honest miners stay | Litepaper Finality; homepage firsts | tested by the team | repo `bbb264a`; `sim/finality_v2.py` | Scenario B of `sim/finality_v2.py`, seeds 7 and 11 | share(t) = (t/30) x a/(1+a) holds to 0.04 points; a renter equal to the whole honest network (a = 1) crosses 1/3 on day 20 and never reaches 2/3; a = 9 crosses 1/3 on day 11.1 and 2/3 on day 22.2. The ten-day figure is a = infinity, honest miners gone. 3 October 2026, Apple M5 Max. A model with 1,000 Pareto keys and no DAG; the harness scenarios against the real node (1b, 3b, 4b) are stubs | none yet |
| 12 | The difficulty rule recovers from a hashrate step within about a minute, where Kaspa's sampled rule never settles | Spec 2.3; litepaper Speed (implied); bench page | tested by the team | repo `e9328c6`; fork worktree `vendor/igneum-node-diff` branch `difficulty`; `sim/difficulty/sim.py` | `sim/difficulty/sim.py` on nine profiles plus the devnet record; the 3-node test network with `"difficulty_rule"` in the override file; `cargo test -p kaspa-consensus --lib difficulty` (9 pass); bench-log "difficulty controller" | Simulator, settled seconds: x50 step 62 (Kaspa 1,542), /50 step 657 (Kaspa 12,296), the devnet's 75x step 79 (Kaspa never). Test network: within 10% of 1 block/s after 132 s warm-up, 214 s on a join, 85 s on a leave. 3 October 2026, Apple M5 Max under load 50 to 98. Monero and LWMA baselines reproduced from memory, approximate; no DAG in the simulator; harness scenarios 2b and 7b are stubs | none yet |
| 13 | Every node executes the ordered transactions natively and reaches the same state root | Litepaper Proving ("Every node executes ... natively"), Building ("runs on Igneum unchanged") | tested by the team | repo `f5f8c80`; fork worktree `vendor/igneum-node-exec` branch `execution-layer`; revm 43.0.3 | `node tools/evm-smoke/smoke.mjs` against a 3-node `igneumd --simnet`; `igneum-exec-diff seq.json`; bench-log "execution layer devnet v3" | 87 of 87 viem checks; state roots identical on 3 nodes at chain blocks 0, 56, 74 and 78; 57 executed transactions and 19 skipped copies agree with plain revm, 0 mismatches; balances match receipts to the wei. 3 October 2026, Apple M5 Max. Simnet skips proof of work, the prover is a stub, state is rebuilt from genesis at start, no EVM transaction relay between nodes | none yet |
| 14 | Ethereum bytecode runs unchanged, with the documented differences of spec 7.1 | Homepage Build card; litepaper Building | tested by the team | as row 13 | `tools/evm-smoke/smoke.mjs`: deploy via viem, `increment`, `hashLoop`, `eth_estimateGas`, `eth_getLogs` | Deployment, calls, reverts, logs and gas estimates behave as viem expects; chain id 4463; the prototype pgas table gives 0.0095 to 0.028 pgas per gas, below the design's band before calibration. 3 October 2026, Apple M5 Max. The `Prover` precompile, proof records and the shard planner are not implemented | none yet |
| 15 | Every block is proven, with the proof landing within about a minute at launch | Homepage stats ("~60 s to a proof"); litepaper Proving; roadmap phase 3 gate | designed | spec 7.2; `docs/design/execution-layer.md` section 5 | None exists. The phase 2 benchmark standard is `docs/benchmarks/proving-e2e.md` | No SP1 shard has been proven on any card in this repository (ledger P1, P3). The devnet prover is a stub that signs claims. The 60-second figure is a design target | none yet |
| 16 | A 12 GB card proves one shard in about 20 s | Litepaper Proving ("The proving budget"); roadmap gate 2 | designed | spec 5.1 (Target) | Replaced as a gate by the end-to-end standard in `docs/benchmarks/proving-e2e.md`: fixed workloads, job-to-accepted-proof latency, no growing backlog | Unmeasured. A per-shard time can be met by shrinking the shard, so the project no longer uses it as a pass mark | none yet |
| 1 | A new mining program every hour, compiled by the miner, with no human in the loop and no pause in mining | Homepage hero and "This hour's program"; litepaper Mining | tested by the team | igneum-pow 0.2.0; repo `b27da39`, `1292110`, `100c5d7`; fork `devnet-v4` `6457ca95` | The live devnet v4: the node announces `next_epoch_seed` 150 DAA past the seed score, `igneum-miner` sends `prepare` to its worker, the worker builds the next program while the current one mines; Metal (`proto-metal/igneum-bench`), CUDA and OpenCL workers; bench-log "first hourly program swap on the live devnet" | Epoch boundary at DAA 3,600 (11:05:07 BST, 4 October 2026) crossed live on three vendors: the Mac M5 Max (Metal) compiled the next program in 82 ms, 449 DAA before the boundary, swapped in 0.01 ms, 26.7 MH/s before and after; the RTX 5090 compiled in 1,285 ms, swapped in 0.00 ms, 121.8 before and 123.4 MH/s after; the integrated AMD chip 2.74 MH/s before and after. 0 restarts, 0 rejected blocks, 0 rebuilds. The epoch seed is the epoch block hash; the delay of row 2 is not wired in. At a later boundary (DAA 18,000) one OpenCL worker on PC 2 stayed on the previous epoch after an app reinstall and answered 514 jobs with a seed mismatch; fixed in the miner (`3bfe346f`, workers emit a `need` line), the swap time of that forced prepare not measured | none yet |
| 2 | The program seed passes through a 10-minute verifiable delay from a certified checkpoint, so nobody can grind the seed | Litepaper Mining, vs RandomX ("Closed by a verifiable delay") | implemented | repo `792776e`; `proto-vdf/` | `proto-vdf` full 10-minute runs and the tamper cases in `proto-vdf/README.md`; bench-log "proto-vdf" | Class group 1024-bit: 163,000 squarings per second, 10-min eval 585.4 s, prove 9.1 s on 12 threads, verify 4.47 ms, 516-byte proof; wrong checkpoint, flipped seed bit and T+1 all rejected; grinding model gains 0 blocks per epoch with the delay against +3.62 at a 30% advantage without it. 3 October 2026, Apple M5 Max, one core. Prototype only: not in the node on 4 October either, not reviewed against chiavdf (O-4.1) | none yet |
| 3 | The dataset is memory-hard: computing an item costs more than loading it, and every hash does 128 distinct dataset reads | Litepaper Mining and vs RandomX; homepage vs RandomX ("Memory 2 GB, growing") | tested by the team | repo `58a5a63` (memory-hard), `b27da39` (generator 2); igneum-pow 0.2.0 (`memhard.rs`, `generator.rs`, `accept.rs`); spec 01 sections 1.4.2 to 1.4.6; `proto-metal/MEMHARD.md` | `proto-metal/igneum-bench --inline-dataset` against the honest run at 1 GiB and 256 MiB; `igneum-census --gen v2 --warps 64` over 20,000 programs; bench-log "memory-hard dataset" and "generator version 2 adopted" | Honest 45.2 Mhash/s, inline (never reads the dataset) 9.49 Mhash/s, ratio 0.21 at 1 GiB, 0.10 at 256 MiB. 3 October 2026, Apple M5 Max. Generator 2, 4 October 2026: 20,000 programs, 128 static loads on every program, distinct addresses per hash mean 127.9, minimum 120.1; 5.2% of candidates rejected by the acceptance rule. The price of the 128 fresh reads is the hash rate: Apple OpenCL 45.0 MH/s on a version 1 program with 80 distinct loads against 27.5 to 27.9 on version 2; the RTX 5090 229 MH/s on a 104-load version 1 program at 1 GiB (3 October) against 121.8 to 124.2 MH/s mining version 2 on the live devnet (4 October). Apple only for the shortcut ratio (O-1.5); the on-die cache question of ledger M16 is unchanged | none yet |
| 4 | The same program produces identical hashes on three GPU vendors, cache and dataset included | Litepaper vs RandomX ("Bit-exact on Apple, NVIDIA and AMD, measured"), For miners; homepage | tested by the team | repo `f2e903e`, `0f1fdaf` (version 1 packs), `b27da39` (version 2 packs `igneum-genesis-mh`, `igneum-devnet-v4-epoch0`); igneum-pow 0.2.0 | The 96 test vectors of a pack through `proto-metal/igneum-bench`, `proto-cuda/host.cu`, `proto-opencl/host.c`; batch fingerprint at `--batch-log2 24`; the miner's CPU re-check of every share a GPU worker finds on the devnet; bench-log entries "RTX 5090, memory-hard dataset", "AMD gfx1036", "RTX 5090 through NVIDIA OpenCL", "generator version 2 adopted", "the gfx1036 worker fault" | Version 1: 96/96 on Apple Metal (M5 Max), NVIDIA CUDA and NVIDIA OpenCL (RTX 5090, Windows), AMD OpenCL (Ryzen 7 9800X3D integrated gfx1036, 1 compute unit), Apple OpenCL, pocl and two CPU references; batch fingerprint `98af644e993239e2` over 16.7 million nonces identical on the AMD chip and the 5090, 3 October 2026. Version 2: 96/96 on Apple Metal, Apple OpenCL and the CUDA and OpenCL emulators with identical fingerprints; on real NVIDIA and AMD silicon the version 2 vectors have not run as a pack, but both mined accepted blocks on the live devnet with the CPU re-check clean on every share (RTX 5090 at 124.2 MH/s, gfx1036 at 3.3 MH/s), 4 October 2026. The AMD device is an integrated chip; no discrete AMD card and no Intel card has run anything (O-1.15) | none yet |
| 5 | A CPU verifies one hash in under 10 ms by simulating one warp | Litepaper Mining ("about ten milliseconds"), vs RandomX; roadmap gate 2 | tested by the team | repo `75cac18`, `b27da39`; igneum-pow 0.2.0 (`verify.rs`) | `cargo test` and the crate bench in `igneum-pow/`; bench-log "igneum-pow: Rust crate bit-exact with proto-metal" and "generator version 2 adopted" | 0.411 to 0.579 ms per 32-lane warp steady, 0.41 to 0.87 ms cold, average of 20, 1 GiB dataset, cache held, one M5 Max performance core, 3 October 2026; version 2 units 0.631 ms (average of 20), cold 0.67 to 0.81 ms, 4 October 2026. Gate margin about 16x on this core. Not measured on a 2019-class laptop core (O-1.14) | none yet |
| 6 | The hash is bound to the header: one nonce serves one header, and a wrong nonce is rejected | Spec 1.6; litepaper Mining (implied by "checks a hash") | tested by the team | repo `33f7b33`, `9812466`, `b27da39`; igneum-pow 0.2.0 (`bind.rs`, bound vectors re-cut for version 2, 39 crate tests) | `igneum-miner bad-nonce` against a devnet node; `igneum-pow hash-bound` for the 96-nonce job across the 2^32 lane boundary; bench-log "first devnet blocks on the real lottery hash" and "generator version 2 adopted" | 833 blocks accepted by `igneum-lottery-v1-bound` on 3 nodes, 0 rejections; `bad-nonce` gave Reject(BlockInvalid); Metal, OpenCL and CUDA (emulated) workers bit-exact with the crate on the lane-boundary job, 3 October 2026, Apple M5 Max. Version 2: the node's engine reports `igneum-lottery-v2-bound`, 39 of 39 crate tests, and the live devnet v4 accepts its blocks under it, 4 October 2026 | none yet |
| 7 | The devnet runs at one block a second | Homepage stats ("1 / s"); litepaper Speed; roadmap phase 3 | tested by the team | repo `9812466`, `e9328c6`, `8dae48b`; fork `devnet-v4` `dc749905` | The merged node's 3-node test network (`igneum-devnet-880`, 960 s); the live devnet v4 record `sim/difficulty/records/live-2026-10-04.csv`; the 12-node cloud network's arrival logs; bench-log "devnet-v4 integration", "difficulty rule v2", "first devnet blocks" | Merged node, 4 October 2026, Apple M5 Max: 1,055 blocks in 960 s, 1.03 blocks/s, sink identical on 3 nodes at 31 of 31 samples, 0 rejected. Live devnet v4 the same day: 49 to 81 blocks a minute while two RTX 5090s joined and left (row 12), 1.1 to 1.2 blocks/s in the oscillating window, then within 1.3% per minute with one PC and the Mac. The 12-node cloud network at one block a second: 644 blocks in a 10-minute window. The 3 October CPU devnet: 1.29 blocks/s over 641 s, 1.03 after the first retarget. The phase 3 gate also asks for proofs under 60 s behind the tip; no proof is on the chain (row 15) | none yet |
| 8 | Blocks are mined by GPUs on Apple and NVIDIA | Homepage live strip; journey phase 3 ("GPU miners on three vendors") | tested by the team | repo `9812466`, `e9328c6`, `d7e1f89`, `2309c8d`; fork `devnet-v4` | Metal worker `proto-metal/igneum-bench --serve` driven by `igneum-miner --worker`; the live devnet v4 hash-rate record `sim/difficulty/records/live-2026-10-04-hashrate.csv` (587 worker STATUS lines by run id); bench-log "first devnet blocks", "devnet v4 cut-over", "difficulty rule v2", "first machine on the Igneum Miner app" | Metal: 506 jobs, 5,636 blocks found and accepted, 0 rejected, 0 CPU/GPU mismatches, 28.2 MH/s wall, 3 October 2026. Live devnet v4, 4 October 2026: PC 1's RTX 5090 at 122 MH/s with 8 identities, PC 2's at 124 MH/s with 8 identities (117 to 119 MH/s inside the one-click app, 34 accepted blocks in its first minute, CPU re-check OK on every share), the Mac's Metal worker at 26.7 MH/s; 17 vote keys signed the first finality lock (row 10). Two RTX 5090s and one M5 Max; no other NVIDIA model has mined | none yet |
| 9 | Blocks are mined by a GPU on AMD | Journey phase 3 ("three vendors") | tested by the team | repo `2c4b30f` (generic OpenCL worker, `--pack`), `112acf6` (fault guards); bound kernel `kernel_bound.cl` in the pack | `igneum-worker-opencl.exe --pack` on PC 2's integrated Radeon against the live devnet v4 through the Windows package; bench-log "the gfx1036 worker fault", "first hourly program swap", "first machine on the Igneum Miner app" | PC 2's integrated gfx1036 (1 compute unit) mined on the live devnet on 4 October 2026: 8 accepted blocks at 3.3 MH/s over 577 s with the CPU re-check clean, and 2.74 MH/s through the hourly program swap with 0 rejected. At about 600 s the AMD runtime began answering every call with success while running nothing (906 jobs became 56,384 in 30 s, 4.3 GH/s of phantom work); not reproduced on Apple OpenCL in 4,565 jobs with 0 leaked objects; the worker and miner now refuse a job 20x faster than the mean or an unchanged output buffer and restart (`112acf6`), and the next gfx1036 run names the guard that fires. One integrated chip; no discrete AMD card has run anything | none yet |
| 10 | Checkpoints lock every 30 s of chain at two thirds of all 30-day weight, and the floor stops conflicting locks in partitions and eclipses for as long as neither side's own new blocks carry it past two thirds of its window (about 10 days of a 30-day window at a 50/50 split) | Litepaper Finality, "What Igneum does not claim"; homepage "locked every 30 seconds" | tested by the team | repo `a3a9833` (2/3 floor, O-3.15), `bbb264a` (simulation), `c16ccf1`; fork `devnet-v4` `6457ca95` (`FLOOR_NUM / FLOOR_DEN` 2/3), `da1eb889` (F17 by-weight sortition, F1 first-month gate `min_daa = window`); spec 3.3, 3.3.1, 3.7, 3.9 | The live devnet v4 (`getFinalityCheckpoints`, `tools/observer/observer.mjs`, `/api/checkpoint`); `sim/finality_v2.py --floor 1.0`, scenarios A to L; the three-node, six-voter partition runs `igneum-devnet-921` to `-923`; `tools/finality-attacks` scenarios 1 to 6 and 8; bench-log "first finality lock on the live devnet", "finality floor 2/3", "finality v2 attack harness", "finality fixes F17 and F1" | Live: the first lock on the live devnet was checkpoint 242 at 12:03:44 BST on 4 October 2026, two hours after genesis (the window and `min_daa` are 7,200 DAA), with 77.4% of all weight and of active weight signed by 12 aggregated votes from 17 vote keys; `observer.mjs` saw it 0.7 s after the miner's own lock line. By 13:21 UTC the observer held 280 certificates, indices 241 to 522 (DAA 7,229 to 17,982), 17 to 27 voters, no index with two hashes. Test networks, 4 October 2026, Apple M5 Max: a 4/2 split locked on the 4 side (67.9%) 2 to 8 s after the cut and never on the 2 side, 0 conflicts; a 3/3 split locked on neither side for 150 s with 0 conflicts, where the 3 October floor (56.7%) would have locked both sides at 76 and 106 s; a 3/3 split held for 205 s crossed the bound (predicted W / (3R) = 200 s) and the two sides certified different checkpoints, 26 conflicting certificates, a finality fork the heal did not undo (ledger F21; the operator override of F5 is unwritten). Simulator with the 2/3 floor: 0 conflicts up to a 33% equivocator (34% splits a 50/50 partition), silent weight pauses locks from 34%, a 50/50 partition locks alone from day 10.1. Harness: equivocating keys stripped on every node, Sybil dust at zero weight, a pulsed miner's weight equal to its block share (ratio 0.96 to 1.0), the first-month gate stops a young window locking under one key. Not demonstrated: certificate injection on the wire, an eclipse with a private fork, the 2-hour presence window at mainnet length | none yet |
| 11 | Hashrate that arrived today has almost no vote: ten days of the whole network's hashrate to reach a third of the weight, twenty for two thirds; 51% never reaches two thirds while honest miners stay | Litepaper Finality; homepage firsts | tested by the team | repo `bbb264a`; `sim/finality_v2.py` | Scenario B of `sim/finality_v2.py`, seeds 7 and 11 | share(t) = (t/30) x a/(1+a) holds to 0.04 points; a renter equal to the whole honest network (a = 1) crosses 1/3 on day 20 and never reaches 2/3; a = 9 crosses 1/3 on day 11.1 and 2/3 on day 22.2. The ten-day figure is a = infinity, honest miners gone. 3 October 2026, Apple M5 Max. A model with 1,000 Pareto keys and no DAG; the live devnet's window is two hours old, so the claim has no live measurement yet | none yet |
| 12 | The difficulty rule recovers from a hashrate step within minutes, where Kaspa's sampled rule never settles. A step inside an epoch set the rule oscillating on the live devnet on 4 October 2026; rule v2 removes it in the simulator and on a test network and is built but not yet rolled out | Spec 2.3; litepaper Speed (implied); bench page | tested by the team | repo `e9328c6`, `abb5a5d` (attacks), `67bf226` (rule v2); fork `difficulty` branch (timestamp fix) and `devnet-v4` `a21ff239` (`difficulty_v2_activation_daa`, `REF_WINDOW_V2 = 600`); `sim/difficulty/sim.py --live` | The live record `sim/difficulty/records/live-2026-10-04.csv` (8,090 headers, `pull_live.py`) and the hash-rate record beside it; `sim/difficulty/sim.py` on the synthetic set and the DAG replay; `sim/difficulty/attacks/attacks.py`; `sim/difficulty/testnet_v2.py` (3 nodes, activation at DAA 900); `cargo test --release -p kaspa-consensus --lib difficulty` (15 pass); bench-log "difficulty controller", "difficulty rule under attack", "timestamp attack fixed", "difficulty rule v2" | Live devnet v4, 4 October 2026 (UTC): a second RTX 5090 joining 7 minutes into an epoch (about 152 to 280 MH/s) hardened the difficulty 70M to 144M in 90 s and then oscillated for 40 minutes, 102M to 164M, peaks of 1.33x every 132 to 150 chain blocks, 54 to 81 blocks a minute, against a true level of 139M; that card leaving for 4 minutes eased 116M to 67M and back to 106M; the epoch boundary with both PCs restarting took 152M to 77M in 3 minutes, after which the rule held within 1.3% per minute with no flips. Cause: the reference lane covered the whole epoch, so a mid-epoch step polluted it for the hour and the 25% trigger flipped on the short lane's noise. The DAG replay reproduces the record (std of log difficulty 0.115 against 0.134, 4.3 peaks against 4). Rule v2 (reference window 600 DAA) on the replay: std 0.026, 0 flips, mean 142.6M against 139M true; on a 3-node test network the v2 nodes eased a leave with no peak and held a rejoin within 3% after 60 s, and a node without the activation height forked off at it as designed. Rollout pending: every devnet node needs the same activation height in its override file (`docs/plans/difficulty-v2-rollout-devnet.md`), the cloud network first. Timestamp forging (ledger M23) fixed the same day: a 50% forger drifts the rate under 1.1% where the 3 October rule gave it a 9.9x difficulty. Simulator, settled seconds: x50 step 62 to 66 (Kaspa 1,542), /50 step 657 to 753 (Kaspa 12,296). Apple M5 Max under load 7 to 442; the DAG model is fitted on one scale; the pool hopper's 0.7-point excess over Kaspa's rule stays open | none yet |
| 13 | Every node executes the ordered transactions natively and reaches the same state root | Litepaper Proving ("Every node executes ... natively"), Building ("runs on Igneum unchanged") | tested by the team | repo `f5f8c80`, `8dae48b`; fork `devnet-v4` `dc749905`; revm 43.0.3 | `node tools/evm-smoke/smoke.mjs` against a 3-node `igneumd`; `igneum-exec-diff seq.json`; bench-log "execution layer devnet v3" and "devnet-v4 integration" | Simnet, 3 October 2026: 87 of 87 viem checks, state roots identical on 3 nodes at four heights, 57 executed and 19 skipped transactions agree with plain revm, 0 mismatches. Merged node on real proof of work, 4 October 2026: 84 of 85 checks (the miss needs parallel blocks the network did not produce in 36 s), 59 transfers in 10 chain blocks, state roots identical on 3 nodes, `igneum-exec-diff` 0 mismatches over 59 transactions; the live devnet v4 runs this execution layer. Apple M5 Max. The prover is a stub; state is rebuilt from genesis at start; no EVM transaction relay between nodes | none yet |
| 14 | Ethereum bytecode runs unchanged, with the documented differences of spec 7.1 | Homepage Build card; litepaper Building | tested by the team | as row 13; fixes `F-exec-A`, `F-exec-B` (spec 7.5) | `tools/evm-smoke/smoke.mjs`: deploy via viem, `increment`, `hashLoop`, `eth_estimateGas`, `eth_getLogs`; `tools/exec-attacks` scenarios 1 and 3; bench-log "execution layer attack fixes" | Deployment, calls, reverts, logs and gas estimates behave as viem expects; chain id 4463; the prototype pgas table gives 0.0095 to 0.028 pgas per gas, below the design's band before calibration, 3 October 2026. 4 October 2026: a transaction that would cross the block's proving budget is refused by the mempool and, if forced in, aborted and charged with its nonce advanced (25 of 25 checks; 30 of 30 malformed cases). Apple M5 Max. The `Prover` precompile, proof records and the shard planner are not in the node | none yet |
| 15 | Every block is proven, with the proof landing within about a minute at launch | Homepage stats ("~60 s to a proof"); litepaper Proving; roadmap phase 3 gate | implemented | repo `d7e1f89` (GPU proof), `e01a3cc`, `292e800`, `eedd136` (`proving/igneum-prove`: shard cutter, MPT witnesses, shard and aggregator guests); SP1 6.8.1; spec 7.2, 7.6 | `proving/windows-wsl2` (SETUP-PROVER, PROVE-BLOCK) on the RTX 5090; `igneum-prove-host --mode block` on `proving/fixtures/`; bench-log "proving v0 on the RTX 5090" and "proving: devnet v4 shards" | First GPU proof of an Igneum block, 4 October 2026, RTX 5090 (WSL2, SP1 cuda, mining paused): fixture `block-78-increment` (2 transactions), core proof 1.4 s (7.3 MB, verify 0.221 s), compressed proof 2.7 s (1.27 MB, verify 0.038 s), post-state and receipts roots identical to the node's; 15.7x and 20.6x faster than a loaded M5 Max CPU. The same day on that CPU (load 38 to 47): a three-shard block proved shard by shard and aggregated by recursion, 19 min (1,139 s) end to end, 245 to 337 s per compressed shard proof, every proof verified. What is not there: no proof is produced, carried or checked on the chain (the devnet prover is a stub that signs claims), the proving pool pays nobody (row 21), the block proven is far below one shard, and the 60-second figure remains a design target; the pass mark is the standard in `docs/benchmarks/proving-e2e.md`. Two host defects (an abort after the upload, a 10-minute idle wait) fixed, to be confirmed on the PC (ledger P20) | none yet |
| 16 | A 12 GB card proves one shard in about 20 s | Litepaper Proving ("The proving budget"); roadmap gate 2 | designed | spec 5.1 (Target), 7.6 (`S_p` provisional, 7,500,000 pgas = `B_p` / 4) | `PROVE-SHARD.bat` on the RTX 5090 (pending); the end-to-end standard in `docs/benchmarks/proving-e2e.md`; bench-log "proving: devnet v4 shards" | Unmeasured on any GPU. A shard at the provisional `S_p` is 60 M SP1 cycles on the prototype pgas table (9 cycles per pgas; the modexp entry about 100x its SP1 cost), executed in 4.6 to 7.7 s on the Mac CPU and not yet proven; the GPU row of the bench-log table is empty until the PC runs it, 4 October 2026. A per-shard time can be met by shrinking the shard, so the project does not use it as a pass mark | none yet |
| 17 | The chip resistance target: a chip gains under 2x over a GPU | Litepaper Mining, "What Igneum does not claim"; homepage "no chip can be built for it" | designed | spec 0.2 (Target); O-1.17 | Public benchmark with a leaderboard by card model and a standing bounty, January 2027 (O-1.17); the on-die-SRAM test on the RTX 5090 (R3.5) | A target, not a measurement. Review round 3 priced a recompute chip with the 256 MiB cache on die at about 2.4x, approximate, before the usual chip-versus-GPU integer gain; the design answer (cache larger than any die) is open (spec 1.16) | none yet |
| 18 | The chip resistance measurements: the program is random-access bound, not bandwidth bound, and sits beyond a card's on-chip cache | Litepaper Mining ("bound by memory bandwidth", to be corrected), vs RandomX "Measured so far" | tested by the team | repo `aba248d`, `f2a1a64`, `4b95c5e` | RTX 5090 dataset sweep 4 MiB to 1 GiB with `proto-cuda/host.cu`; bench-log "RTX 5090 first run" and "dataset sweep" | At 1 GiB: 228.1 Mhash/s, 23.7 G random loads/s, 94.9 GB/s useful against a 1,638 GB/s dataset fill; inside the 96 MiB L2 (4 and 64 MiB) 1,340 to 1,353 Mhash/s, about 5.8x faster; 104 against 128 loads per hash gives 228 against 185 Mhash/s, proportional. 3 October 2026, RTX 5090, Windows, CUDA 12.8. Prototype dataset 1 GiB against 2 GB at genesis; a pure random-read microbenchmark (R3 chip designer, attack 2) has not run | none yet |
| 19 | The lottery hash is sound as a hash: uniform output, deterministic, no out-of-bounds read, fuzzed | Litepaper vs RandomX ("Every number above is measured and logged") | tested by the team | repo `c52307e`, `58a5a63`; `proto-metal/TESTS.md` | `proto-metal/igneum-bench --fuzz --edge --stats --determinism --memcheck`; bench-log "hardening tests" and the re-run on the memory-hard dataset | 10,200 random programs, 1,305,600 hashes, 0 mismatches; 14 of 14 edge cases; bit frequency within 2.90 sigma, avalanche mean 31.99 to 32.04 of 32; deterministic fingerprint across 5 runs; every dataset read masked. 3 October 2026, Apple M5 Max. Statistics are not a security proof; the weak-program census (O-1.3) and the seed derivation review (O-1.4) are open; the fuzz set has run on Metal and the CPU only | none yet |
| 18 | The chip resistance measurements: the program is random-access bound, not bandwidth bound, and sits beyond a card's on-chip cache | Litepaper Mining ("bound by memory bandwidth", to be corrected), vs RandomX "Measured so far" | tested by the team | repo `aba248d`, `f2a1a64`, `4b95c5e` | RTX 5090 dataset sweep 4 MiB to 1 GiB with `proto-cuda/host.cu`; bench-log "RTX 5090 first run" and "dataset sweep" | At 1 GiB: 228.1 Mhash/s, 23.7 G random loads/s, 94.9 GB/s useful against a 1,638 GB/s dataset fill; inside the 96 MiB L2 (4 and 64 MiB) 1,340 to 1,353 Mhash/s, about 5.8x faster; 104 against 128 loads per hash gives 228 against 185 Mhash/s, proportional. 3 October 2026, RTX 5090, Windows, CUDA 12.8, version 1 programs. Prototype dataset 1 GiB against 2 GB at genesis; a pure random-read microbenchmark (R3 chip designer, attack 2) has not run; the sweep has not been repeated on version 2 | none yet |
| 19 | The lottery hash is sound as a hash: uniform output, deterministic, no out-of-bounds read, fuzzed | Litepaper vs RandomX ("Every number above is measured and logged") | tested by the team | repo `c52307e`, `58a5a63`, `b27da39`; `proto-metal/TESTS.md` | `proto-metal/igneum-bench --fuzz --edge --stats --determinism --memcheck`; `--fuzz 2000` on the version 2 generator; `igneum-census`; bench-log "hardening tests", the re-run on the memory-hard dataset, "generator version 2 adopted" | Version 1: 10,200 random programs, 1,305,600 hashes, 0 mismatches; 14 of 14 edge cases; bit frequency within 2.90 sigma, avalanche mean 31.99 to 32.04 of 32; deterministic fingerprint across 5 runs; every dataset read masked, 3 October 2026. Version 2, 4 October 2026: 2,000 random programs through the Metal cross-check, 8,000 warps, 0 mismatches, 128 loads per hash on every program; 20,000-program census, 5.2% rejected (4.1% static, 1.1% dynamic). Apple M5 Max. Statistics are not a security proof; the edge, stats and memcheck sections were not re-run on version 2 (they do not depend on the generator); the seed derivation review (O-1.4) is open; the fuzz set has run on Metal and the CPU only | none yet |
| 20 | No premine, no pre-sale, no allocation: every coin is minted by the schedule and every coin goes to the block producer (80%) and the proving pool (20%) | Homepage stats and Economics tiles; litepaper Supply, Economics | implemented | repo `6ac80a3`; fork "igneum-node devnet v0"; `consensus/core/src/igneum.rs`, `coinbase.rs` | `cargo test -p kaspa-consensus-core igneum` (8 pass: subsidy table, ramp, split, cap) and `cargo test -p kaspa-consensus coinbase` (8 pass); `igneum-miner inspect 40`; bench-log "igneum-node devnet v0" | Coinbases on the devnet: 80/20 exact on 39 of 39 single-payee blocks, the 20% to the `igneum-proving-pool-v0` output; the per-second schedule sums to under the 4,000,000,000 cap by less than 100 coins; 3,168,808,781 units per DAA second in years 0 to 2, halving at 63,115,200 DAA s. 3 October 2026, Apple M5 Max. The devnet genesis carries no allocation; the mainnet genesis does not exist yet, so the claim is about the code and the stated rule, not a launch that has happened | none yet |
| 21 | The proving pool's 20% reaches shard provers and aggregators | Litepaper Economics; homepage "20% provers" | designed | spec 5.3 | None. The pool output exists (row 20); the payout from it against proof records is unwritten | The escrow accumulated on the simnet (92.55 IGN at the end of the v3 run) and nothing can draw it. Rule decided: per block, divided among shards by consensus proving cost, sortition to 8 provers for 10 s then open (spec 7.2) | none yet |
| 21 | The proving pool's 20% reaches shard provers and aggregators | Litepaper Economics; homepage "20% provers" | designed | spec 5.3; `proving/igneum-prove` carries the prover's payout address in every shard proof (ledger P12) | None. The pool output exists (row 20); the payout from it against proof records is unwritten | The escrow accumulated on the simnet (92.55 IGN at the end of the v3 run) and nothing can draw it. Rule decided: per block, divided among shards by consensus proving cost, sortition to 8 provers for 10 s then open (spec 7.2). The economy model of 4 October 2026 (`sim/economy`, 1,000 operators, 30 days) kept every block proven within 60 s under six stress scenarios; a model, not hardware | none yet |
| 22 | The base fee is burned in full and the priority fee splits 80% to the miner and provers, 20% to the apps whose code ran | Homepage Economics caption and Build card; litepaper "Where fees go" | tested by the team | repo `f5f8c80`; fork worktree `vendor/igneum-node-exec` | `tools/evm-smoke/smoke.mjs` receipt checks; bench-log "execution layer devnet v3" | Transfer receipt: `burnedProvingFee` 200 gwei, `minerTip` 16,800 gwei (80%), unregistered developer share 4,200 gwei burned; contract call: 80% to the miner, 20% credited to the payee the constructor registered, balance delta equal. 3 October 2026, Apple M5 Max simnet. The provers' part of the 80% is not split out (no provers exist); the base fee stayed at the 1 gwei floor throughout | none yet |
| 23 | No fee to any team, foundation or fund; 0 admin keys in consensus | Homepage Economics tiles and caption; litepaper "No fund, no foundation" and Governance | designed | spec 5.5, 5.6 (decided 3 October 2026); spec 08 | Reading: no coinbase output, fee route or consensus key in the fork names any party (`coinbase.rs`, `docs/fork-divergence.md`) | The emission code has two outputs (row 20) and the fee code has three routes (row 22), none to a team. The 1% fee of the official client is a client setting, not a protocol rule, and is not implemented (no client exists). The release key of spec 08 signs client updates and holds no consensus power; its custody policy is open (O-8.1) | none yet |
| 23 | No fee to any team, foundation or fund; 0 admin keys in consensus | Homepage Economics tiles and caption; litepaper "No fund, no foundation" and Governance | designed | spec 5.5, 5.6 (decided 3 October 2026); spec 08 | Reading: no coinbase output, fee route or consensus key in the fork names any party (`coinbase.rs`, `docs/fork-divergence.md`) | The emission code has two outputs (row 20) and the fee code has three routes (row 22), none to a team. The 1% fee of the official client is a client setting, not a protocol rule, and is not implemented. The release key of spec 08 signs client updates (the Igneum Miner app's over-the-air manifest since 4 October 2026, Ed25519) and holds no consensus power; its custody policy is open (O-8.1) | none yet |
| 24 | External proving jobs pay 90% to the provers who delivered and burn 10%, once settled in IGN | Homepage "IGN burned from jobs, phase two"; litepaper Proving and Economics | designed | spec 5.4 | None. Needs the proof bridge (spec 7.3, phase two) and the settlement switch (O-5.2) | At launch jobs are paid on the customer's chain in the customer's currency and nothing is burned (ledger P10). No job market code exists | none yet |
| 25 | The 4 billion cap, halving every two years, with a 30-day ramp from 10% | Homepage "4B IGN hard cap"; litepaper Supply and the emission chart | tested by the team | repo `6ac80a3`; fork `consensus/core/src/igneum.rs` | `cargo test -p kaspa-consensus-core igneum`; bench-log "igneum-node devnet v0" | Ramp day 0 paid 10.03% of the full rate (317,767,704 units at DAA 806); the schedule table and the cap assert in the crate's own tests. 3 October 2026, Apple M5 Max. Base unit (8 or 18 decimals) is open (O-2.6); the spec was changed to follow the code's 365.25-day year (ledger E9) and a test that reads the published numbers back is still owed | none yet |
| 26 | A phone or browser verifies the chain from a locked checkpoint, at about 3.44 MB per day in checkpoint mode | Homepage "Browser checks Igneum" card (preview, commit `f874f80`); litepaper Building ("Light clients"), firsts row 6 | designed | spec 10 (10.5 bytes per day: 3.44 MB at 1,000 voters, 6.68 MB at 10,000, derived, approximate); repo `f874f80` for the browser card | None for the byte figure; `site/verify/` for the card. BLS verification on a phone and in WebAssembly is O-10.3; the full-header mode on a phone is O-10.4 | The homepage card verifies the latest certified checkpoint's BLS certificate in the tab against a voter list from the node (light client v0, 3 October 2026). The byte figure is arithmetic on designed sizes (header 400 bytes, proof 400 bytes), measured nowhere; the proof the card would check does not exist (row 15) | none yet |
| 27 | The node survives malformed input, floods, withholding, partitions and eclipses | Litepaper Speed ("GHOSTDAG, the BlockDAG consensus proven on Kaspa"); spec 2 | tested by the team | repo `394030c`; fork worktree `vendor/igneum-node-harness`; `tools/harness/` | `tools/harness/` scenario runner against a private `igneumd` test network (ports 27200+); bench-log "consensus attack harness" | 63 malformed cases, node up on every one; timestamp bounds exact; withholding at 10%, 25%, 33% and 45% released every 5 blocks within 2 sigma of share; partitions of 120 s to 3,700 s healed to one chain in 10 s; eclipse victims rejoined in 10 s; 50x template, submit and mempool floods left template p95 under 4 ms. One FAIL: a 45% withholder releasing every 20 blocks took 50.7% of blues (bound 47.4%). 3 October 2026, Apple M5 Max, load 50 to 61. Finality and difficulty scenarios are stubs until those branches merge | none yet |
| 28 | Headers are validated cheaply before the lottery engine runs, so forged timestamps cannot force 256 MiB cache builds | Spec 2.4; ledger M15 | tested by the team | repo `0953ec7`; fork worktree `vendor/igneum-node-r3` branch `r3-fixes` at `5166ee26` | `measure_m15_attack_before_and_after` (ignored test, release, `--features igneum-pow`); kaspa-pow 8, header_processor 1, p2p `pow_guard` 2 tests | 50 forged headers: before, 50 cold builds in 10,595 ms and the live day evicted; after, 0 builds, all 50 rejected in 14 ms. 3 October 2026, Apple M5 Max under load 60 to 110. Measured through the validate path with `skip_proof_of_work`, not the daemon RPC; not merged into the main fork branch | none yet |
| 26 | A phone or browser verifies the chain from a locked checkpoint, at about 3.44 MB per day in checkpoint mode | Homepage "Browser checks Igneum" card; litepaper Building ("Light clients"), firsts row 6 | designed | spec 10 (10.5 bytes per day: 3.44 MB at 1,000 voters, 6.68 MB at 10,000, derived, approximate); repo `f874f80` for the browser card; `site/api/checkpoint.mjs` | None for the byte figure; `site/verify/` for the card against `/api/checkpoint`. BLS verification on a phone and in WebAssembly is O-10.3; the full-header mode on a phone is O-10.4 | Since 12:03 BST on 4 October 2026 the homepage card verifies the live devnet's own certificates in the tab (index 522 with 27 voters at 13:42 UTC), BLS aggregate against the voter list the node serves, light client v0; before that it verified the 3 October test network's. The byte figure is arithmetic on designed sizes (header 400 bytes, proof 400 bytes), measured nowhere; the execution proof the card would also check is not on the chain (row 15) | none yet |
| 27 | The node survives malformed input, floods, withholding, partitions and eclipses | Litepaper Speed ("GHOSTDAG, the BlockDAG consensus proven on Kaspa"); spec 2 | tested by the team | repo `394030c`, `8dae48b`, `6b5bd92`; fork worktree `vendor/igneum-node-harness` and `devnet-v4`; `tools/harness/`; `infra/cloud-devnet/experiments/partition.sh` | `tools/harness/` against a private `igneumd` test network; the merged node's harness scenarios 2 and 5; the cloud network's 10-minute partition of Singapore (`results/2026-10-04/partition-sin-20261004-110906/partition.md`); bench-log "consensus attack harness", "devnet-v4 integration" | 3 October 2026, Apple M5 Max: 63 malformed cases, node up on every one; withholding at 10% to 45% within 2 sigma of share; partitions of 120 s to 3,700 s healed to one chain in 10 s; eclipse victims rejoined in 10 s; 50x floods left template p95 under 4 ms; one FAIL, a 45% withholder releasing every 20 blocks took 50.7% of blues (bound 47.4%). Merged node, 4 October 2026: 63 cases, node up, 0 cache builds; the 10 s timestamp floor and future bound exact. Cloud network, 4 October 2026: 12 nodes in five locations on their own chain, Singapore cut off by iptables for 10 minutes; the two minority nodes adopted the majority chain 10 and 14 s after the heal with reorgs of 445 and 516 blocks, the majority's deepest reorg was 2 blocks, 0 conflicting locks (none were possible: the weight window stood at DAA 3,030 of 7,200). CPU miners only; the finality rules under partition are row 10 | none yet |
| 28 | Headers are validated cheaply before the lottery engine runs, so forged timestamps cannot force 256 MiB cache builds | Spec 2.4; ledger M15 | tested by the team | repo `0953ec7`, `8dae48b`; fork worktree `vendor/igneum-node-r3` branch `r3-fixes` at `5166ee26`, merged into `devnet-v4` | `measure_m15_attack_before_and_after` (ignored test, release, `--features igneum-pow`); kaspa-pow 8, header_processor 1, p2p `pow_guard` 2 tests; harness scenario 5 on the merged node | 50 forged headers: before, 50 cold builds in 10,595 ms and the live day evicted; after, 0 builds, all 50 rejected in 14 ms, 3 October 2026, Apple M5 Max under load 60 to 110. Merged node, 4 October 2026: 63 harness cases with 0 cache builds (the node log shows one build, the honest day) and the M15 p2p cases disconnected by the strike guard; the live devnet v4 runs it. Measured through the validate path with `skip_proof_of_work`, not the daemon RPC | none yet |
| 29 | Blocks reach every node well inside GHOSTDAG's delay bound across continents | Litepaper Speed (GHOSTDAG at one block a second); spec 03 C1 (lock latency); `infra/cloud-devnet/README.md` | tested by the team | repo `6b5bd92`; `infra/cloud-devnet/experiments/latency.sh`, `analyze.py`; the Linux cross-build `infra/cross/build-linux.sh` | 12 `igneumd` nodes on Hetzner VMs in Helsinki, Falkenstein, Ashburn, Hillsboro and Singapore (own chain `igneum-devnet-20`, one CPU trickle miner each), a ping matrix, then 10 minutes of per-node arrival logs joined on block hash; `results/2026-10-04/latency/propagation.md` and `rtt-by-region.md` | 644 blocks in the window, 642 seen by at least 80% of nodes; arrival at a node minus the first arrival anywhere: p50 343 ms, p90 497 ms, p99 666 ms, max 2,313 ms; by region p50 239 ms (Falkenstein) to 413 ms (Singapore), p90 455 to 632 ms; inter-region RTT 35 ms (Helsinki to Falkenstein) to 289 ms (Ashburn to Singapore); first arrival minus header time median 490 ms. 4 October 2026. The network is the project's own: 12 nodes not 20 (a new account's limits), CPU hash rate only, clocks by chrony, one evening of data; the 5 s bound behind GHOSTDAG k is a design parameter this run did not challenge | none yet |
| 30 | One click: install, press start, the card mines; the app looks after its node | Homepage Mine section ("One click: install, press start"); litepaper "One click, for everyone else"; journey phase 5 | tested by the team | repo `3bb50d6`, `2c4b30f`, `6461540` (package 0.3.0: prebuilt NVRTC CUDA worker and generic OpenCL worker, driver only), `a1a33cb`, `7c794df`, `0d4498e`, `6c083db` (Igneum Miner 0.3.0), `78903cd` (0.3.1, over-the-air updates) | `Igneum-Miner-Setup-0.3.0.exe` (runner-built, unsigned) on a Windows PC with an RTX 5090 and no toolchain; `proto-cuda/nvrtc/emu/serve-check.sh` on the Mac; `proto-cuda/windows-app/TEST.md`; bench-log "one-click Windows workers", "first machine on the Igneum Miner app", "a node 60 s behind the clock is silently dead", "the gfx1036 worker fault" | One machine so far, PC 2, 4 October 2026. The NVRTC worker compiled the pack on the card with no toolchain installed and mined at 124.2 MH/s, equal to the nvcc-built worker, 0 rejected, CPU re-check clean; inside the app 117 to 119 MH/s with 34 accepted blocks in the first minute, the integrated AMD chip at 3.3 MH/s beside it (row 9). Two defects found by the install, both fixed the same hour: a clock 62 s slow after a power cut made the node reject every relayed block for 12 minutes with no visible reason (the app now reads the skew from the node's warnings, the block timestamps over the EVM RPC and an HTTPS Date header, warns over 5 s and blocks Start over 10 s, with a one-click clock sync; checked on the Mac with a fake 60 s skew; a one-line node warning is filed), and the node card said "syncing" while the miner was already accepted. The Mac could only emulate the NVIDIA path (17 of 17 sampled hashes) and the AMD path on Apple OpenCL (15 of 15). Over-the-air updates were dry-run on a private devnet (0.3.0 to 0.3.1 and back), not on a user's machine. The installer is unsigned (SmartScreen "run anyway"). The live devnet's three GPU machines (two PCs and the Mac) run the same workers; one of them through the app | none yet |
## Count by status
| Status | Rows |
|---|---|
| designed | 7 (rows 15, 16, 17, 21, 23, 24, 26) |
| implemented | 3 (rows 2, 9, 20) |
| tested by the team | 18 (rows 1, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 18, 19, 22, 25, 27, 28) |
| designed | 6 (rows 16, 17, 21, 23, 24, 26) |
| implemented | 3 (rows 2, 15, 20) |
| tested by the team | 21 (rows 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 18, 19, 22, 25, 27, 28, 29, 30) |
| reproduced externally | 0 |
| reviewed independently | 0 |
28 rows. The rendered page is `site/evidence.html`, kept in step by hand with this file; the bench page is generated, this one is not, because its text is judgement, not a log.
30 rows. The rendered page is `site/evidence.html`, kept in step by hand with this file; the bench page is generated, this one is not, because its text is judgement, not a log.
## What moved on 4 October 2026
| Row | Before | After | Why |
|---|---|---|---|
| 1 | tested by the team (one vendor, 3 October) | tested by the team (three vendors on the live devnet, no pause) | the first hourly swap on the live devnet |
| 3 | 80 to 112 loads with repeats | 128 distinct reads per hash, the rate cost stated | generator version 2 |
| 9 | implemented | tested by the team | the integrated AMD chip mined accepted blocks on the live devnet |
| 10 | "the floor stops conflicting locks" | "... for as long as neither side's own blocks carry it past two thirds of its window" | the 205-s split on a full window certified both sides (F21) |
| 12 | "recovers within about a minute" | "within minutes; a mid-epoch step oscillated for 40 minutes; v2 built, rollout pending" | the live oscillation of 4 October |
| 15 | designed | implemented | a GPU proved a block, off-chain |
| 26 | test-network certificate | live devnet certificate | the first live lock |
| 29, 30 | new | tested by the team | the cloud network's propagation run; the one-click app on PC 2 |
## What would move a row

View file

@ -0,0 +1,73 @@
# Shards ready to test on PC 2: the signed job channel
4 October 2026. the project lead's ask: shards ready to test on PC 2 (RTX 5090, WSL2 Ubuntu 24.04, the prover toolchain installed
this morning, the Igneum Miner app running as machine `1ccfe586`) while he is away, and from then on "one app on both
PCs that you can send over the line commands to and files so we can continually test everything and build without
input". PC 2 has no Claude session and nobody at the keyboard, so the line is the app itself: a signed job channel
next to the over-the-air manifest. The relay (`relay/`) stays for the Mac and for humans; its PC agent is replaced.
## What is built (this commit)
| Piece | Where | State |
|---|---|---|
| Job model: parse, Ed25519 verify (OTA key), targeting (machine id 8 or 16 hex or all, platform, requirements), expiry, once-per-id ledger, helpers | `app/igneum-app/src/jobs.rs` | 7 unit tests pass on the Mac |
| Runner: 10-minute poll, requirement probes (`wsl`, `wsl-prover`, `nvidia`), the kinds `run`, `fetch`, `collect`, `restart`, `update-now`, `shard-benchmark`, reports to the log intake as `job-<id>-<machine id8>` with a `SUMMARY {json}` first line, 5-minute progress reports, time caps, abort on quit | `app/igneum-app/src/jobrun.rs` | compiles for macOS and `x86_64-pc-windows-gnu`; not yet run on a PC |
| Engine hooks: `Cmd::Job`, miners stopped and held for a job, node restart, app relaunch, updater on demand; no OTA apply under a running job | `app/igneum-app/src/engine.rs` | |
| Dashboard: the job strip (running: "Job: shard benchmark running, N min" plus the RESULT lines as they arrive; afterwards the outcome for 30 minutes), Settings: "Allow remote jobs from Igneum (signed)" ON by default with the key fingerprint, Check now, the history table (id, kind, started, exit, report) | `app/igneum-app/ui/` | |
| Signer: `sign-jobs`, `verify-jobs` (refuses a file with a bad job) | `app/igneum-app/src/bin/ota-sign.rs` | run with the real key: signs, verifies, refuses a tampered file and a bad job |
| Publisher: `publish-jobs.sh add|list|remove|sign [--deploy]` | `packaging/ota/publish-jobs.sh` | run against a scratch folder: add of all kinds, list, remove, duplicate id refused |
| Reader: the published file (signature checked), status per machine, a job's result, watch | `tools/jobs.mjs` | reads the live intake (no job reports yet) |
| Relay playbook, the same run in its relay form (reference, and the model for a `run` job) | `relay/playbooks/shard-test.ps1` | parse-checked by `windows.yml` (folder added to the check) |
The prove package: `~/Desktop/igneum-prove-wsl2.zip` (286,438 bytes, sha256 `5e7b56f5...950373`) is byte-identical to
the hosted `dl/<token>/igneum-prove-wsl2.zip`, and its contents match `proving/windows-wsl2/`, `proving/igneum-prove/`
(sources and Cargo.lock) and `proving/fixtures/` (338, 341, 344 and the three test fixtures); the only difference is
the thiserror pin `make-package.sh` writes into evm-types. No rebuild was needed.
## The first job (after 0.3.2 is on PC 2)
The app on PC 2 is 0.3.1 today. The job channel is in the build after it: the project lead cuts 0.3.2 with the miner fix, the
OTA installs it, then:
packaging/ota/publish-jobs.sh add --kind shard-benchmark --target 1ccfe586 --title "Shard proof run on the 5090" --deploy
This hosts the zip (already there), signs the file, deploys the downloads folder and verifies the live file. Within
10 minutes (or at once from Settings > remote jobs > Check now) PC 2's app: stops its miners (the node keeps
running), waits for the GPU under 5%, downloads the zip into `%LOCALAPPDATA%\igneum\prove`, extracts it fresh, runs
`wsl -d Ubuntu-24.04 -- bash /mnt/c/.../prove-shard.sh block-338-shard1 "block-341-shards2 block-344-shards4"`
under a 90-minute cap, uploads every RESULT and STAGE line, the `results/*.json` and the prove log, and restarts the
miners. Options: `--fixtures "..."`, `--cap-minutes`, `--wsl-user [user]` (when [user] is not the distro's default
user), `--distro`.
Watching from the Mac:
node tools/jobs.mjs the published file, signature checked
node tools/jobs.mjs status the latest job per machine with its SUMMARY line
node tools/jobs.mjs watch <job id> every 30 s until final; the result files land as labels result-<name>
node tools/logs.mjs the app's own uploads (the engine log shows the job's events too)
The RESULT lines fill the empty GPU column of the bench-log skeleton ("shard proving on the RTX 5090"). The first
`S_p` point comes from the shard stage times (`docs/plans/proving-v0.md`).
## Everything else over the same line
publish-jobs.sh add --kind run --target 1ccfe586 --script x.ps1 [--elevated] [--stop-miners] [--timeout-minutes 60]
publish-jobs.sh add --kind fetch --target all --file ~/Desktop/thing.zip --dir prove --extract --fresh --extract-dir thing
publish-jobs.sh add --kind collect --target all --glob "logs/app-*.log" --glob "prove/igneum-prove-wsl2/results/*.json" --command "nvidia-smi"
publish-jobs.sh add --kind restart --target ae432dc7 --what miners|node|app
publish-jobs.sh add --kind update-now --target all
A machine runs an id once; the same thing again is a new add. Jobs expire (48 hours by default) and are dropped
from the file on the next write. Everything a job writes stays under the app data folder except what a `run`
script does, which is the operator's responsibility. `elevated` on an unattended PC fails after the UAC prompt
times out: that needs someone to click, or UAC set to elevate without prompting.
## Not verified from the Mac
| What | Why | How it gets checked |
|---|---|---|
| The runner on Windows: wsl.exe output through the sink, `taskkill /T` on the cap, the elevated wrapper | no PC reachable from this session | the first `shard-benchmark` and a `run` job on PC 2 after 0.3.2; the engine log (`app-*.log`) carries every step |
| That `[user]` is the distro's default user (the probe and prove-shard.sh run as the default user unless `wsl_user` is set) | not visible from here | if the probe fails with cargo missing, re-add the job with `--wsl-user [user]` |
| The dashboard strip and the Settings block in a real window | the dashboard needs the engine's API; only the JS parse and the state shape were checked | the first job on either PC |
| The 90-minute cap against a cold build (first run compiles 10 to 30 minutes, approximate) | the toolchain was pre-built this morning in `~/igneum-prove`, and prove-shard.sh rsyncs the same sources over it, so the build should be incremental | the STAGE timestamps in the report |
| The relay: PC 2 is not registered (both PCs report the hostname DESKTOP-KMCV30N; the relay's PC1 is whichever started `igneum-agent.bat`), so no relay task was queued | by the project lead's change of plan the relay agent is not the channel for the PCs | the playbook stays as the relay form |

View file

@ -105,3 +105,57 @@ over a folder written by `publish-manifest.sh --base-url ... --dest ...`; loopba
parser accepts), found the 0.3.1 manifest, downloaded and verified the DMG, staged the bundle, waited for the worker
to start, applied, and came back as 0.3.1 with "updated to Igneum Miner 0.3.1 from 0.3.0" in the event feed. The
screenshots are `docs/design/app-screens/update-*.png`. Windows: reviewed only, see `TEST.md`.
## Remote jobs (4 October 2026)
the project lead's rule: one app on both PCs that the Mac can send commands and files to over the line, so everything is tested
and built without a person at the PC. The channel is `igneum-jobs.json` plus `igneum-jobs.json.sig`, next to the
update manifest, signed with the same OTA key and verified by the same code; the apps poll it every 10 minutes.
The relay (`relay/`) stays for the Mac and for humans; its PC agent is replaced by this.
| Piece | Where |
|---|---|
| model: parse, verify, targeting, expiry, the once-per-id ledger (`jobs-state.json` in the app data folder), unit tests | `app/igneum-app/src/jobs.rs` |
| runner: poll, requirement probes, the kinds, reports, the dashboard state | `app/igneum-app/src/jobrun.rs` |
| signer: `igneum-ota-sign sign-jobs` and `verify-jobs` (a bad job is refused at signing) | `app/igneum-app/src/bin/ota-sign.rs` |
| publisher: `packaging/ota/publish-jobs.sh add|list|remove|sign [--deploy]` | this folder |
| reader: the published file (signature checked), status per machine, a job's result, watch | `tools/jobs.mjs` |
A job: `{id, kind, title, created_at, expires_at, target: {machine_ids: [...] | "all", platform, requires}, params,
report: "log-intake"}`. Machine ids are the per-install id (16 hex) or its first 8 (PC 1 `ae432dc7`, PC 2
`1ccfe586`). Requirements the engine probes: `wsl`, `wsl-prover` (cargo, `~/.sp1` and `~/igneum-prove` inside
Ubuntu-24.04), `nvidia`; an unknown one is never met and the job waits until it expires.
| Kind | What the app does | Params |
|---|---|---|
| `run` | writes the script body to `<app data>/app/jobs/<id>/` and runs it (powershell or bash), output captured, exit code reported | `script`, `shell`, `elevated`, `stop_miners_first`, `timeout_minutes` (60, at most 600) |
| `fetch` | downloads by https, checks the sha256 (and size), into the job folder or a named app folder; can extract | `url`, `sha256`, `size`, `dir` jobs/prove/packs/updates, `to`, `extract`, `extract_dir`, `fresh` |
| `collect` | uploads files matching globs under the app data root, and/or a command's output, to the intake | `globs`, `command` |
| `restart` | miners, node (the miners follow) or the app (a detached helper opens it again) | `what` |
| `update-now` | the updater checks and installs a newer version at once | |
| `shard-benchmark` | miners stopped, GPU under 5%, the prove zip fetched fresh, `prove-shard.sh` inside WSL under the cap, RESULT and STAGE lines and `results/*.json` uploaded, miners back | `zip_url`, `sha256`, `size`, `fixtures`, `cap_minutes` (90), `distro`, `wsl_user` |
Reports: every job uploads to the log intake as run_id `job-<id>-<machine id8>` with the label `job-<kind>`; the
first line is `SUMMARY {json}` (status, exit, times, the RESULT lines, per-kind extras), then the captured output.
Long jobs upload a running report every 5 minutes; collected files and result files are separate labels
(`file-<name>`, `result-<name>`, `prove-log`) under the same run_id.
Safety: the file is rejected when the signature or any job's `expires_at` or params fail; a job id runs once per
machine (a crash mid-job counts); nothing writes outside the app data folder except an explicit `run` script,
which is the operator's responsibility; the dashboard shows the running job and a history (id, kind, started, exit,
report uploaded); Settings has "Allow remote jobs from Igneum (signed)", ON by default on this devnet build, with
the key fingerprint, and OFF aborts the running job. `elevated` needs someone at the UAC prompt (or UAC set to
elevate without prompting); unattended it fails after the prompt times out. A `restart app` or `update-now` job
relaunches through the usual quit path.
Publishing and reading:
packaging/ota/publish-jobs.sh add --kind shard-benchmark --target 1ccfe586 --title "Shard proof run on the 5090" --deploy
packaging/ota/publish-jobs.sh add --kind run --target ae432dc7,1ccfe586 --script fix.ps1 --title "..." [--elevated] [--stop-miners]
packaging/ota/publish-jobs.sh list | remove <id> | sign
node tools/jobs.mjs | status | <id> [--all] | watch <id>
Tested on the Mac, 4 October 2026: the unit tests (parse, bad jobs refused, signature and tampering, times,
targeting, the once-only ledger, globs), the signer with the real key, the publisher against a scratch folder, the
reader against the live intake; the crate also compiles for `x86_64-pc-windows-gnu`. Not yet run on a PC: the
first job goes to PC 2 (`1ccfe586`) once 0.3.2 is installed there (`docs/plans/shard-test-pc2.md`).

253
packaging/ota/publish-jobs.sh Executable file
View file

@ -0,0 +1,253 @@
#!/usr/bin/env bash
# Publishes signed remote jobs for the Igneum Miner apps: igneum-jobs.json (canonical JSON, sorted keys, no
# whitespace) and its detached Ed25519 signature igneum-jobs.json.sig, next to the update manifest in the downloads
# folder (dl/<token>/), signed on this Mac with the OTA key ~/.config/igneum/ota-signing-key. Every app polls the
# file every 10 minutes (app/igneum-app/src/jobrun.rs), verifies it with the public key compiled into
# src/manifest.rs, runs each job that targets it ONCE per id, and reports to the log intake as
# run_id job-<id>-<machine id8> (read back with tools/jobs.mjs).
#
# packaging/ota/publish-jobs.sh add --kind run --target 1ccfe586 --script path.ps1 [--elevated] [--stop-miners] \
# [--timeout-minutes 60] [--shell powershell|bash] --title "..." [--expires-hours 48] [--deploy]
# packaging/ota/publish-jobs.sh add --kind fetch --target all --file ~/Desktop/x.zip [--dir jobs|prove|packs|updates] \
# [--to name] [--extract] [--extract-dir sub] [--fresh] (or --url https://... --sha256 ... [--size N])
# packaging/ota/publish-jobs.sh add --kind collect --target all --glob "logs/app-*.log" [--glob ...] [--command "nvidia-smi"]
# packaging/ota/publish-jobs.sh add --kind restart --target 1ccfe586 --what miners|node|app
# packaging/ota/publish-jobs.sh add --kind update-now --target all
# packaging/ota/publish-jobs.sh add --kind shard-benchmark --target 1ccfe586 [--zip ~/Desktop/igneum-prove-wsl2.zip] \
# [--fixtures "block-338-shard1 block-341-shards2 block-344-shards4"] [--cap-minutes 90] [--distro Ubuntu-24.04] [--wsl-user [user]]
# common: --target <id8 or id16, comma list, or all> [--platform windows|mac|any] [--requires wsl-prover,nvidia]
# [--id custom-id] [--title "..."] [--expires-hours 48] [--deploy]
# packaging/ota/publish-jobs.sh list what is published (expired jobs marked)
# packaging/ota/publish-jobs.sh remove <id> [--deploy]
# packaging/ota/publish-jobs.sh sign [--deploy] re-sign the file as it is (expired jobs dropped)
#
# Jobs already in the file stay (expired ones are dropped on every write). A machine runs an id once: to run the
# same thing again, add it again (a new id is generated from the kind and the time unless --id is given).
# Without --deploy the script prints the deploy command; with --deploy it runs the Vercel CLI from the downloads
# folder and verifies the live file. Testing: --dest <folder> writes elsewhere; --base-url overrides the file URLs.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
export PATH="$HOME/.cargo/bin:$PATH"
KEY="$HOME/.config/igneum/ota-signing-key"
PUB="$HOME/.config/igneum/ota-signing-key.pub"
TOKEN_FILE="$HOME/.config/igneum/dl-token"
SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign"
CMD="${1:-}"; [ $# -gt 0 ] && shift
KIND="" TARGET="" PLATFORM="" REQUIRES="" ID="" TITLE="" EXPIRES_H="48" DEPLOY=0 BASE="" DEST=""
SCRIPT="" SHELL_KIND="" ELEVATED=0 STOP_MINERS=0 TIMEOUT_MIN=""
FILE="" URL="" SHA="" SIZE="" DIR="" TO="" EXTRACT=0 EXTRACT_DIR="" FRESH=0
GLOBS=() COMMAND="" WHAT=""
ZIP="" FIXTURES="" CAP_MIN="" DISTRO="" WSL_USER="" REMOVE_ID=""
case "$CMD" in
remove) REMOVE_ID="${1:-}"; [ -n "$REMOVE_ID" ] || { echo "remove <id>" >&2; exit 2; }; shift ;;
esac
while [ $# -gt 0 ]; do
case "$1" in
--kind) KIND="$2"; shift 2 ;;
--target) TARGET="$2"; shift 2 ;;
--platform) PLATFORM="$2"; shift 2 ;;
--requires) REQUIRES="$2"; shift 2 ;;
--id) ID="$2"; shift 2 ;;
--title) TITLE="$2"; shift 2 ;;
--expires-hours) EXPIRES_H="$2"; shift 2 ;;
--script) SCRIPT="$2"; shift 2 ;;
--shell) SHELL_KIND="$2"; shift 2 ;;
--elevated) ELEVATED=1; shift ;;
--stop-miners) STOP_MINERS=1; shift ;;
--timeout-minutes) TIMEOUT_MIN="$2"; shift 2 ;;
--file) FILE="$2"; shift 2 ;;
--url) URL="$2"; shift 2 ;;
--sha256) SHA="$2"; shift 2 ;;
--size) SIZE="$2"; shift 2 ;;
--dir) DIR="$2"; shift 2 ;;
--to) TO="$2"; shift 2 ;;
--extract) EXTRACT=1; shift ;;
--extract-dir) EXTRACT_DIR="$2"; shift 2 ;;
--fresh) FRESH=1; shift ;;
--glob) GLOBS+=("$2"); shift 2 ;;
--command) COMMAND="$2"; shift 2 ;;
--what) WHAT="$2"; shift 2 ;;
--zip) ZIP="$2"; shift 2 ;;
--fixtures) FIXTURES="$2"; shift 2 ;;
--cap-minutes) CAP_MIN="$2"; shift 2 ;;
--distro) DISTRO="$2"; shift 2 ;;
--wsl-user) WSL_USER="$2"; shift 2 ;;
--deploy) DEPLOY=1; shift ;;
--no-deploy) DEPLOY=0; shift ;;
--base-url) BASE="$2"; shift 2 ;;
--dest) DEST="$2"; shift 2 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
case "$CMD" in add|list|remove|sign) ;; *) sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; esac
[ -f "$KEY" ] || { echo "no $KEY (see packaging/ota/README.md, Keys)" >&2; exit 1; }
[ -f "$PUB" ] || { echo "no $PUB" >&2; exit 1; }
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE" >&2; exit 1; }
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
if [ -z "$DEST" ]; then
DLSITE="${IGNEUM_DLSITE:-}"
[ -n "$DLSITE" ] || { [ -f "$HOME/.config/igneum/dlsite-dir" ] && DLSITE="$(tr -d '[:space:]' < "$HOME/.config/igneum/dlsite-dir")"; } || true
[ -n "$DLSITE" ] && [ -d "$DLSITE/dl/$TOKEN" ] || { echo "no downloads folder: set IGNEUM_DLSITE or ~/.config/igneum/dlsite-dir (must hold dl/<token>/)" >&2; exit 1; }
DEST="$DLSITE/dl/$TOKEN"
else
DLSITE=""
mkdir -p "$DEST"
fi
[ -n "$BASE" ] || BASE="https://dl.igneum.network/dl/$TOKEN"
BASE="${BASE%/}"
JOBS="$DEST/igneum-jobs.json"
command -v python3 >/dev/null || { echo "python3 is needed for the canonical JSON" >&2; exit 1; }
if [ ! -x "$SIGNER" ]; then
echo "building igneum-ota-sign"
(cd "$ROOT/app/igneum-app" && nice -n 19 cargo build --release -j 4 --bin igneum-ota-sign --quiet)
fi
EMBEDDED="$("$SIGNER" embedded | head -1)"
OURS="$(tr -d '[:space:]' < "$PUB")"
if [ "$EMBEDDED" != "$OURS" ]; then
echo "the public key in app/igneum-app/src/manifest.rs ($EMBEDDED) is not $PUB ($OURS); the apps would refuse this file" >&2
exit 1
fi
if [ "$CMD" = list ]; then
[ -f "$JOBS" ] || { echo "no jobs file in $DEST"; exit 0; }
"$SIGNER" verify-jobs "$PUB" "$JOBS" "$JOBS.sig" || { echo "the file in $DEST does not verify; run: $0 sign" >&2; exit 1; }
python3 - "$JOBS" <<'PY'
import json, sys, datetime
f = json.load(open(sys.argv[1]))
now = datetime.datetime.now(datetime.timezone.utc)
for j in f.get("jobs", []):
exp = datetime.datetime.strptime(j["expires_at"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=datetime.timezone.utc)
print((" EXPIRED " if exp < now else " ") + j["id"] + ": " + json.dumps(j.get("params", {}), sort_keys=True)[:200])
PY
exit 0
fi
# ---- the new job (add) -------------------------------------------------------------------------------------------
NEW_JOB=""
hosted_file() { # <local file> -> "url sha256 size" (copied into the downloads folder when it is not there)
local f="$1" name
[ -f "$f" ] || { echo "missing: $f" >&2; exit 1; }
name="$(basename "$f")"
if [ "$(cd "$(dirname "$f")" && pwd)/$name" != "$DEST/$name" ]; then
cp "$f" "$DEST/$name"
echo "copied $name into the downloads folder (deploy ships it)" >&2
fi
read -r sum size < <("$SIGNER" sha256 "$DEST/$name")
echo "$BASE/$name $sum $size"
}
if [ "$CMD" = add ]; then
[ -n "$KIND" ] || { echo "--kind is required" >&2; exit 2; }
[ -n "$TARGET" ] || { echo "--target is required (id8, id16, a comma list, or all)" >&2; exit 2; }
PARAMS='{}'
case "$KIND" in
run)
[ -n "$SCRIPT" ] && [ -f "$SCRIPT" ] || { echo "run: --script <file> is required" >&2; exit 2; }
[ -n "$SHELL_KIND" ] || { case "$SCRIPT" in *.sh) SHELL_KIND=bash ;; *) SHELL_KIND=powershell ;; esac; }
[ -n "$PLATFORM" ] || { [ "$SHELL_KIND" = powershell ] && PLATFORM=windows || PLATFORM=any; }
PARAMS="$(python3 -c 'import json,sys; print(json.dumps({"script": open(sys.argv[1]).read(), "shell": sys.argv[2], "elevated": sys.argv[3]=="1", "stop_miners_first": sys.argv[4]=="1", **({"timeout_minutes": int(sys.argv[5])} if sys.argv[5] else {})}))' "$SCRIPT" "$SHELL_KIND" "$ELEVATED" "$STOP_MINERS" "$TIMEOUT_MIN")"
[ -n "$TITLE" ] || TITLE="run $(basename "$SCRIPT")"
;;
fetch)
if [ -n "$FILE" ]; then read -r URL SHA SIZE < <(hosted_file "$FILE"); fi
[ -n "$URL" ] && [ -n "$SHA" ] || { echo "fetch: --file <local> or --url and --sha256" >&2; exit 2; }
PARAMS="$(python3 -c 'import json,sys; a=sys.argv; d={"url": a[1], "sha256": a[2]}
if a[3]: d["size"]=int(a[3])
if a[4]: d["dir"]=a[4]
if a[5]: d["to"]=a[5]
if a[6]=="1": d["extract"]=True
if a[7]: d["extract_dir"]=a[7]
if a[8]=="1": d["fresh"]=True
print(json.dumps(d))' "$URL" "$SHA" "$SIZE" "$DIR" "$TO" "$EXTRACT" "$EXTRACT_DIR" "$FRESH")"
[ -n "$TITLE" ] || TITLE="fetch $(basename "$URL")"
;;
collect)
[ ${#GLOBS[@]} -gt 0 ] || [ -n "$COMMAND" ] || { echo "collect: --glob and/or --command" >&2; exit 2; }
PARAMS="$(python3 -c 'import json,sys; d={"globs": [g for g in sys.argv[2:] if g]}
if sys.argv[1]: d["command"]=sys.argv[1]
print(json.dumps(d))' "$COMMAND" "${GLOBS[@]:-}")"
[ -n "$TITLE" ] || TITLE="collect ${GLOBS[*]:-command output}"
;;
restart)
case "$WHAT" in miners|node|app) ;; *) echo "restart: --what miners|node|app" >&2; exit 2 ;; esac
PARAMS="$(python3 -c 'import json,sys; print(json.dumps({"what": sys.argv[1]}))' "$WHAT")"
[ -n "$TITLE" ] || TITLE="restart $WHAT"
;;
update-now)
[ -n "$TITLE" ] || TITLE="update now"
;;
shard-benchmark)
[ -n "$ZIP" ] || ZIP="$HOME/Desktop/igneum-prove-wsl2.zip"
read -r ZURL ZSHA ZSIZE < <(hosted_file "$ZIP")
[ -n "$PLATFORM" ] || PLATFORM=windows
[ -n "$REQUIRES" ] || REQUIRES=wsl-prover
PARAMS="$(python3 -c 'import json,sys; a=sys.argv; d={"zip_url": a[1], "sha256": a[2], "size": int(a[3])}
if a[4]: d["fixtures"]=a[4].split()
if a[5]: d["cap_minutes"]=int(a[5])
if a[6]: d["distro"]=a[6]
if a[7]: d["wsl_user"]=a[7]
print(json.dumps(d))' "$ZURL" "$ZSHA" "$ZSIZE" "$FIXTURES" "$CAP_MIN" "$DISTRO" "$WSL_USER")"
[ -n "$TITLE" ] || TITLE="shard benchmark on the GPU"
;;
*) echo "unknown kind $KIND (run, fetch, collect, restart, update-now, shard-benchmark)" >&2; exit 2 ;;
esac
[ -n "$PLATFORM" ] || PLATFORM=any
[ -n "$ID" ] || ID="$KIND-$(date -u +%Y%m%d-%H%M%S)"
NEW_JOB="$(python3 -c 'import json,sys,datetime
a=sys.argv
now=datetime.datetime.now(datetime.timezone.utc)
t={"machine_ids": "all" if a[2]=="all" else [x.strip().lower() for x in a[2].split(",") if x.strip()], "platform": a[3]}
if a[4]: t["requires"]=[x.strip() for x in a[4].split(",") if x.strip()]
print(json.dumps({"id": a[1], "kind": a[5], "title": a[6], "created_at": now.strftime("%Y-%m-%dT%H:%M:%SZ"), "expires_at": (now+datetime.timedelta(hours=float(a[7]))).strftime("%Y-%m-%dT%H:%M:%SZ"), "target": t, "params": json.loads(a[8]), "report": "log-intake"}))' "$ID" "$TARGET" "$PLATFORM" "$REQUIRES" "$KIND" "$TITLE" "$EXPIRES_H" "$PARAMS")"
fi
# ---- merge: current jobs minus expired (minus a removed id), plus the new one; canonical JSON ---------------------
NEW="$JOBS.new"
python3 - "$JOBS" "$NEW" "$NEW_JOB" "$REMOVE_ID" <<'PY'
import json, sys, datetime, os
cur, out, new_job, remove = sys.argv[1:5]
now = datetime.datetime.now(datetime.timezone.utc)
jobs = []
if os.path.exists(cur):
for j in json.load(open(cur)).get("jobs", []):
exp = datetime.datetime.strptime(j["expires_at"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=datetime.timezone.utc)
if exp < now:
print("dropped (expired):", j["id"], file=sys.stderr); continue
if remove and j["id"] == remove:
print("removed:", j["id"], file=sys.stderr); continue
jobs.append(j)
if new_job:
nj = json.loads(new_job)
if any(j["id"] == nj["id"] for j in jobs):
print("a job with id %s is already published; give --id another value" % nj["id"], file=sys.stderr); sys.exit(1)
jobs.append(nj)
print("added:", nj["id"], nj["kind"], "to", nj["target"]["machine_ids"], "until", nj["expires_at"], file=sys.stderr)
f = {"published_at": now.strftime("%Y-%m-%dT%H:%M:%SZ"), "jobs": jobs}
open(out, "w").write(json.dumps(f, sort_keys=True, separators=(",", ":"), ensure_ascii=False))
PY
"$SIGNER" sign-jobs "$KEY" "$NEW" > "$NEW.sig"
"$SIGNER" verify-jobs "$PUB" "$NEW" "$NEW.sig"
mv "$NEW" "$JOBS"
mv "$NEW.sig" "$JOBS.sig"
echo "jobs file: $JOBS ($(wc -c < "$JOBS" | tr -d ' ') bytes)"
if [ "$DEPLOY" = 1 ]; then
[ -n "$DLSITE" ] || { echo "--deploy needs the real downloads folder (no --dest)" >&2; exit 1; }
echo "deploying $DLSITE"
(cd "$DLSITE" && npx --yes vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes 2>&1 | grep -v "$TOKEN" || true)
TMP="$(mktemp -d)"
curl -fsSL -o "$TMP/j.json" "$BASE/igneum-jobs.json" && curl -fsSL -o "$TMP/j.sig" "$BASE/igneum-jobs.json.sig" \
&& cmp -s "$TMP/j.json" "$JOBS" && "$SIGNER" verify-jobs "$PUB" "$TMP/j.json" "$TMP/j.sig" >/dev/null && echo "live jobs file verified at $BASE/igneum-jobs.json" \
|| { echo "the live jobs file is not reachable, differs from the local one, or does not verify yet; check the deploy output" >&2; rm -rf "$TMP"; exit 1; }
rm -rf "$TMP"
echo "the apps pick it up within 10 minutes (Settings > remote jobs > Check now at once); results: node tools/jobs.mjs ${ID:-<id>}"
else
if [ -n "$DLSITE" ]; then
echo "not deployed: cd $DLSITE && npx --yes vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes (or re-run with --deploy)"
else
echo "written to $DEST for $BASE (test file; not the downloads folder)"
fi
fi

View file

@ -1,6 +1,26 @@
# Igneum relay
Text, files and tasks between the project lead's devices without Gmail: the Mac, PC1, PC2 and the phone post to one feed and read from it. Vercel project `igneum-relay`, served at https://relay.igneum.network. Built 4 October 2026.
Text, files and tasks between the project lead's devices without Gmail: the Mac, PC1, PC2 and the phone post to one feed and read from it. Vercel project `igneum-relay`, served at https://relay.igneum.network. Built 4 October 2026. Since the evening of 4 October 2026 the same private page is the Igneum console (`ui.html`): seven tabs, phone first, refreshed every 15 s. The relay feed and drop box are its last tab.
**Scope since 4 October 2026 (afternoon):** the relay stays for the Mac and for humans (notes, files, tasks for a person or a Claude session on a PC). Commands and files for the PCs themselves go over the line to the Igneum Miner app instead: signed jobs published next to the update manifest (`packaging/ota/publish-jobs.sh`, read back with `tools/jobs.mjs`, documented in `packaging/ota/README.md`, "Remote jobs"). The app jobs replace the PC agent (`igneum-agent.bat`): PC 2 has no Claude session and nobody at the keyboard, and both PCs report the same hostname (DESKTOP-KMCV30N), which the relay's registration cannot tell apart; the app's per-install machine id can. The playbooks under `relay/playbooks/` stay as the relay form of the same runs (`shard-test.ps1` is the model for a `run` job) and are parse-checked by `windows.yml`.
## The console
| Tab | Shows | Source |
|---|---|---|
| Machines | one card per machine: app and node version, height (daa), synced, hash rate, accepted blocks, peers, faults, power and temperatures, last seen; red after 3 min without an upload | Neon `miner_logs` (the log intake in `site/api/log.mjs`): newest upload per label, the last 20 KB parsed server side (miner `STATUS` lines, node log, the app's `stability:` lines) |
| Jobs | the signed jobs file with per-machine status (queued, running, done + exit code) and the result line; tap a run for the full upload | `igneum-jobs.json` on the downloads host (fetched server side with `DL_TOKEN`), results from `miner_logs` rows whose `run_id` is `job-<id>-<machine>` |
| Builds | the OTA manifest (version, notes, platforms, sizes), the last CI fetch, build events, the downloads folder listing | `igneum-app-latest.json` and `igneum-windows-ci.json` on the downloads host; `console_items` kind `build` (posted by `packaging/windows/fetch-ci-artifacts.sh` and `packaging/ota/publish-manifest.sh`) and key `dl` (`tools/console.mjs sync-dl`) |
| Chain | blocks, identities, hash estimate, difficulty, last lock, finality state, peers, blocks per minute sparkline, events, Hetzner results | `https://igneum.network/api/live` fetched server side; `console_items` key `hetzner` (`sync-hetzner`) |
| Work log | what the agents and the main session post, merged with every relay item, newest first | `console_items` kinds `log`, `build`, `note`; the relay feed |
| Results | the bench log entries (heading + first paragraph), newest first; the FUD ledger counts by status | `console_items` kind `bench` and key `ledger`, written by `tools/console.mjs sync-bench` from `docs/bench-log.md` and `docs/fud-ledger.md` |
| Relay | the feed and the drop box, unchanged | `relay_items`, `relay_machines` |
The console function is `api/console.mjs`, reached through the rewrite `/r/<token>/c/<fn>`. Every GET answer is cached 10 s in the function instance. No secret reaches the client: the token in the path is the only auth, and `DL_TOKEN` (the downloads folder) lives in the project env and is used only server side. No GitHub token anywhere: build events come from the Mac-side scripts.
Mac: `node tools/console.mjs post --kind log --title "..." --body "..."` writes one work-log item (kinds `log`, `build`, `note`); `log`, `machines`, `chain`, `jobs`, `builds`, `results` print the tabs; `sync-bench`, `sync-dl`, `sync-hetzner` or `sync` push the file-derived data; `url` prints the link.
Known gap (4 Oct 2026): the app log (label `win-<id8>` or `mac-<id8>`) never reaches the intake, because `app/igneum-app/src/main.rs` names the file with its own `stamp_now()` while `engine.rs` uploads `app-<engine stamp>.log`. Until that is fixed the Machines tab has no app version, no `stability:` power and temperature lines and no `status:` lines; everything else comes from the node and miner logs.
## The secret is the path

View file

@ -0,0 +1,164 @@
# Igneum playbook: the shard proof run on the RTX 5090, end to end, unattended. Mac-side reference, 4 October 2026.
# What it does: stops the Igneum Miner app cleanly (stop-igneum.ps1 from the installed app, else the engine's local
# API, else the process), waits for the GPU to go idle, downloads the hosted prove package fresh into C:\igneum-prove,
# runs the toolchain setup only when cargo or ~/.sp1 is missing inside WSL, runs prove-shard.sh non-interactively
# (the one-shard fixture at S_p, then the two- and four-shard blocks) under a 90-minute budget for the whole run,
# posts the results/*.json and the log to the relay as a result of this task, relaunches the app and prints RELAY-DONE.
# Queue: node tools/relay.mjs run PC2 "Shard proof run on the 5090" relay/playbooks/shard-test.ps1 --elevated
# The same work ships as the app's signed `shard-benchmark` job (app/igneum-app/src/jobrun.rs); this file stays as the
# relay form of it and as the model for a `run` job. UNTESTED on a PC as of 4 Oct 2026 (parse-checked by windows.yml).
$ErrorActionPreference = 'Continue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$budgetMinutes = 90
$started = Get-Date
$deadline = $started.AddMinutes($budgetMinutes)
$distro = 'Ubuntu-24.04'
$wslUser = '[user]'
$zipUrl = '__DL_BASE__/igneum-prove-wsl2.zip'
$root = 'C:\igneum-prove'
$pkg = Join-Path $root 'igneum-prove-wsl2'
$zip = Join-Path $env:TEMP 'igneum-prove-wsl2.zip'
$shardFixture = 'block-338-shard1'
$blockFixtures = 'block-341-shards2 block-344-shards4'
if ($env:SHARD_FIXTURE) { $shardFixture = $env:SHARD_FIXTURE }
if ($env:BLOCK_FIXTURES) { $blockFixtures = $env:BLOCK_FIXTURES }
$appDir = Join-Path $env:ProgramFiles 'Igneum Miner'
$taskId = 0
if ($env:RELAY_TASK_ID) { $taskId = [long]$env:RELAY_TASK_ID }
$rc = 1
function Strip([string] $s) { if ($null -eq $s) { return '' }; return ($s -replace "`0", '') }
function Say([string] $m) { Write-Host ("[" + (Get-Date -Format 'HH:mm:ss') + "] " + $m) }
function Minutes-Left { return [int][Math]::Floor(($deadline - (Get-Date)).TotalMinutes) }
function Post-File([string] $path, [string] $title) {
if (-not $env:RELAY_SEND) { Say ("no RELAY_SEND; not posting " + $path); return }
if (-not (Test-Path $path)) { return }
try { & $env:RELAY_SEND -TaskId $taskId -Title $title $path 2>&1 | ForEach-Object { Say (" " + (Strip "$_")) } } catch { Say ("post failed for " + $path + ": " + $_.Exception.Message) }
}
function Gpu-Util {
$smi = Get-Command nvidia-smi -ErrorAction SilentlyContinue
if (-not $smi) { $alt = Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe'; if (Test-Path $alt) { $smi = $alt } else { return -1 } }
try {
$out = & $smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>$null
$vals = @($out | ForEach-Object { [int]("$_".Trim()) })
if ($vals.Count -eq 0) { return -1 }
return ($vals | Measure-Object -Maximum).Maximum
} catch { return -1 }
}
function Stop-App {
$stop = Join-Path $appDir 'stop-igneum.ps1'
if (Test-Path $stop) {
Say ("stopping the miner app with " + $stop)
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $stop 2>&1 | ForEach-Object { Say (" " + (Strip "$_")) }
return
}
$urlFile = Join-Path $env:LOCALAPPDATA 'igneum\app\app.url'
if (Test-Path $urlFile) {
$url = (Get-Content $urlFile -Raw).Trim()
if ($url) {
try { Invoke-WebRequest -Uri ($url + 'api/quit') -Method POST -Body '{}' -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null; Say 'asked the engine to quit over its local API' } catch { Say ('local API did not answer: ' + $_.Exception.Message) }
}
}
$until = (Get-Date).AddSeconds(50)
while ((Get-Date) -lt $until) {
if (@(Get-Process -Name 'igneum-app', 'igneumd' -ErrorAction SilentlyContinue).Count -eq 0) { break }
Start-Sleep -Milliseconds 500
}
foreach ($name in @('Igneum Miner', 'igneum-app', 'igneum-miner', 'igneum-worker-cuda', 'igneum-worker-opencl', 'igneumd')) {
Get-Process -Name $name -ErrorAction SilentlyContinue | ForEach-Object { Say ("ending " + $_.ProcessName + " pid " + $_.Id); Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue }
}
}
function Start-App {
$lnk = Join-Path $env:ProgramData 'Microsoft\Windows\Start Menu\Programs\Igneum Miner\Igneum Miner.lnk'
$exe = Join-Path $appDir 'igneum-app.exe'
if (Test-Path $lnk) {
# explorer.exe starts the shortcut with the signed-in user's token, not the elevated one of this task
Say ("relaunching the app from " + $lnk)
Start-Process explorer.exe -ArgumentList ("`"" + $lnk + "`"")
} elseif (Test-Path $exe) {
Say ("relaunching " + $exe + " --launch")
Start-Process -FilePath $exe -ArgumentList '--launch' -WorkingDirectory $appDir
} else {
Say 'no installed app found to relaunch (no Start Menu shortcut, no Program Files\Igneum Miner\igneum-app.exe)'
return
}
Start-Sleep 20
$alive = @(Get-Process -Name 'igneum-app', 'igneumd' -ErrorAction SilentlyContinue | ForEach-Object { $_.ProcessName + ' pid ' + $_.Id })
if ($alive.Count -gt 0) { Say ("app running: " + ($alive -join ', ')) } else { Say 'WARNING: the app did not come back within 20 s' }
}
try {
Say ("shard proof run on " + $env:COMPUTERNAME + ", budget " + $budgetMinutes + " min, fixtures " + $shardFixture + " then " + $blockFixtures)
# 1. the miner app off the GPU
Stop-App
$until = (Get-Date).AddSeconds(120)
while ((Get-Date) -lt $until) {
$u = Gpu-Util
if ($u -lt 0) { Say 'nvidia-smi gave no reading; waiting 10 s and going on'; Start-Sleep 10; break }
if ($u -lt 5) { Say ("GPU idle at " + $u + "%"); break }
Say ("GPU still at " + $u + "%"); Start-Sleep 3
}
# 2. the prove package, fresh
Say ("downloading " + $zipUrl)
Invoke-WebRequest -Uri $zipUrl -OutFile $zip -UseBasicParsing -TimeoutSec 600
New-Item -ItemType Directory -Force -Path $root | Out-Null
if (Test-Path $pkg) { Remove-Item $pkg -Recurse -Force }
Expand-Archive -Path $zip -DestinationPath $root -Force
$script = Join-Path $pkg 'prove-shard.sh'
if (-not (Test-Path $script)) { throw ("prove-shard.sh missing under " + $pkg + " after the extract") }
$linuxPkg = '/mnt/c/igneum-prove/igneum-prove-wsl2'
# 3. the toolchain: setup only when it is missing
$probe = 'command -v cargo && ls ~/.sp1 && ls ~/igneum-prove'
& wsl.exe -d $distro -u $wslUser -- bash -lc $probe 2>&1 | ForEach-Object { Say (" probe: " + (Strip "$_")) }
if ($LASTEXITCODE -ne 0) {
Say 'toolchain missing inside WSL: running setup-wsl.sh (15 to 40 minutes, approximate; needs sudo without a password)'
& wsl.exe -d $distro -u $wslUser -- bash -lc ("sudo -n true 2>/dev/null || echo 'sudo asks for a password: the setup will fail'; cd " + $linuxPkg + " && bash ./setup-wsl.sh") 2>&1 | ForEach-Object { Say (" setup: " + (Strip "$_")) }
Say ("setup-wsl.sh exit " + $LASTEXITCODE)
} else {
Say 'toolchain present (cargo, ~/.sp1, ~/igneum-prove)'
}
# 4. the proof run, under what is left of the budget (5 minutes kept for the upload and the relaunch)
$left = (Minutes-Left) - 5
if ($left -lt 10) { throw ("only " + $left + " minutes of the budget left before the proof run; not starting it") }
Say ("running prove-shard.sh with " + $left + " minutes")
$outLog = Join-Path $pkg 'prove-shard-stdout.log'
$errLog = Join-Path $pkg 'prove-shard-stderr.log'
$p = Start-Process -FilePath 'wsl.exe' -ArgumentList @('-d', $distro, '-u', $wslUser, '--', 'bash', ($linuxPkg + '/prove-shard.sh'), $shardFixture, ('"' + $blockFixtures + '"')) -WorkingDirectory $pkg -NoNewWindow -PassThru -RedirectStandardOutput $outLog -RedirectStandardError $errLog
$proveDeadline = (Get-Date).AddMinutes($left)
$lastShown = 0
while (-not $p.HasExited) {
if ((Get-Date) -ge $proveDeadline) {
Say 'time budget reached: ending the proof run'
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
& wsl.exe -d $distro -u $wslUser -- bash -c 'pkill -f igneum-prove-host; pkill -f prove-shard.sh; true' 2>&1 | Out-Null
break
}
Start-Sleep 30
if (Test-Path $outLog) {
$lines = @(Get-Content $outLog -ErrorAction SilentlyContinue)
if ($lines.Count -gt $lastShown) {
$lines[$lastShown..($lines.Count - 1)] | Where-Object { $_ -match '^(RESULT|STAGE|===|BUILD FAILED|building|upload)' } | ForEach-Object { Say (" " + (Strip "$_")) }
$lastShown = $lines.Count
}
}
}
if ($p.HasExited) { $rc = $p.ExitCode; Say ("prove-shard.sh exit " + $rc) } else { $rc = 124 }
# 5. the results to the relay
Say 'RESULT and STAGE lines:'
if (Test-Path $outLog) { Select-String -Path $outLog -Pattern '^(RESULT|STAGE|BUILD FAILED)' | ForEach-Object { Say (" " + (Strip $_.Line)) } }
$results = Join-Path $pkg 'results'
if (Test-Path $results) {
Get-ChildItem $results -Filter '*.json' | Where-Object { $_.LastWriteTime -ge $started } | ForEach-Object { Post-File $_.FullName ("prove result " + $_.Name) }
}
Get-ChildItem $pkg -Filter 'prove-shards-*.log' | Sort-Object LastWriteTime | Select-Object -Last 1 | ForEach-Object { Post-File $_.FullName 'prove-shard log' }
Post-File $outLog 'prove-shard stdout'
} catch {
Say ("ERROR: " + $_.Exception.Message)
$rc = 1
} finally {
# 6. mining back, whatever happened above
Start-App
Say ("done after " + [int]((Get-Date) - $started).TotalMinutes + " min, exit " + $rc)
Write-Host 'RELAY-DONE'
}
exit $rc

View file

@ -71,48 +71,50 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c
<div class="wrap">
<header><a class="brand" href="/"><svg viewBox="0 0 100 100" width="26" height="26" aria-hidden="true"><polygon points="50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34" fill="#F2541B"></polygon><polygon points="50,42 59,58 50,82 41,58" fill="#0C0C0E"></polygon></svg><b>IGNEUM</b></a><nav><a href="/">Home</a><a href="/litepaper">Litepaper</a><a href="/live">Live devnet</a><a href="/bench">Engineering log</a><a href="/evidence" aria-current="page">Evidence</a><a href="https://github.com/igneum-network/spec" rel="noopener">GitHub, spec and vectors</a></nav></header>
<h1>Evidence</h1>
<p class="note">Every claim the homepage and the litepaper make, one row each, with one of five labels: <b>designed</b> (a decision, no code), <b>implemented</b> (code with passing test vectors), <b>tested by the team</b> (measured by the project on a named machine, in the engineering log), <b>reproduced externally</b> (a third party ran the published command and got the published result) and <b>reviewed independently</b> (a named outside reviewer published a finding on that version). Nothing on this chain has been reproduced externally or reviewed independently; every row says so. A label belongs to the exact version in the row, and an audit of one version never covers a newer one.</p>
<p class="note">Every claim the homepage and the litepaper make, one row each, with one of five labels: <b>designed</b> (a decision, no code), <b>implemented</b> (code with passing test vectors), <b>tested by the team</b> (measured by the project on a named machine, in the engineering log), <b>reproduced externally</b> (a third party ran the published command and got the published result) and <b>reviewed independently</b> (a named outside reviewer published a finding on that version). Nothing on this chain has been reproduced externally or reviewed independently; every row says so. A label belongs to the exact version in the row, and an audit of one version never covers a newer one. The 12-node cloud network of 4 October 2026 is the project's own, so its rows are tested by the team, not reproduced externally.</p>
<div class="labels">
<div class="label"><div class="k">designed</div><div class="v">7</div><p>A decision in the design document or the specification. No code, or a stub</p></div>
<div class="label"><div class="k">designed</div><div class="v">6</div><p>A decision in the design document or the specification. No code, or a stub</p></div>
<div class="label"><div class="k">implemented</div><div class="v">3</div><p>Code in the repository with test vectors that pass. Not measured as the claim</p></div>
<div class="label"><div class="k">tested by the team</div><div class="v">18</div><p>Measured or exercised by the project on a named machine, with the command in the log</p></div>
<div class="label"><div class="k">tested by the team</div><div class="v">21</div><p>Measured or exercised by the project on a named machine, with the command in the log</p></div>
<div class="label"><div class="k">reproduced externally</div><div class="v">0</div><p>None yet. The repository is private until January 2027</p></div>
<div class="label"><div class="k">reviewed independently</div><div class="v">0</div><p>None yet. What review would cost and who pays is in the funding plan</p></div>
</div>
<div class="chips" role="group" aria-label="Filter by status"><button type="button" class="chip on" data-filter=""><b>28</b> all</button><button type="button" class="chip" data-filter="designed"><b>7</b> designed</button><button type="button" class="chip" data-filter="implemented"><b>3</b> implemented</button><button type="button" class="chip" data-filter="tested by the team"><b>18</b> tested by the team</button><button type="button" class="chip" data-filter="reproduced externally"><b>0</b> reproduced externally</button><button type="button" class="chip" data-filter="reviewed independently"><b>0</b> reviewed independently</button></div>
<div class="chips" role="group" aria-label="Filter by status"><button type="button" class="chip on" data-filter=""><b>30</b> all</button><button type="button" class="chip" data-filter="designed"><b>6</b> designed</button><button type="button" class="chip" data-filter="implemented"><b>3</b> implemented</button><button type="button" class="chip" data-filter="tested by the team"><b>21</b> tested by the team</button><button type="button" class="chip" data-filter="reproduced externally"><b>0</b> reproduced externally</button><button type="button" class="chip" data-filter="reviewed independently"><b>0</b> reviewed independently</button></div>
<div class="tbl"><table id="claims">
<thead><tr><th data-col="0" data-num="1">#</th><th data-col="1">Claim</th><th data-col="2" data-status="1">Status</th><th data-col="3">Version or commit</th><th data-col="4">Reproducible test</th><th data-col="5">Result, date, machine</th><th data-col="6">Independent verification</th></tr></thead>
<tbody>
<tr data-status="tested by the team"><td class="n">1</td><td class="claim">A new mining program every hour, compiled by the miner, with no human in the loop<div class="where">Homepage hero and "This hour's mining program"; litepaper Mining</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">igneum-pow 0.1.0; repo <code>9812466</code>; fork "PoW: header-bound lottery engine, real-hash miner modes, genesis bits 0x1e400000"</td><td><code>igneum-miner mine --engine igneum-pow</code> against a 3-node <code>igneumd --devnet</code>, with <code>proto-metal/igneum-bench --serve</code> as the GPU worker; bench-log "first devnet blocks on the real lottery hash"</td><td>Epoch change crossed live at DAA 3,600: new seed, a 128-load program compiled by the Metal worker in 129 ms, 0 rejected blocks across the change. 3 October 2026, Apple M5 Max. The epoch seed on the devnet is the epoch block hash; the VDF of row 2 is not wired in yet</td><td class="iv">none yet</td></tr>
<tr data-status="implemented"><td class="n">2</td><td class="claim">The program seed passes through a 10-minute verifiable delay from a certified checkpoint, so nobody can grind the seed<div class="where">Litepaper Mining, vs RandomX ("Closed by a verifiable delay")</div></td><td><span class="st st-1">implemented</span></td><td class="mono">repo <code>792776e</code>; <code>proto-vdf/</code></td><td><code>proto-vdf</code> full 10-minute runs and the tamper cases in <code>proto-vdf/README.md</code>; bench-log "proto-vdf"</td><td>Class group 1024-bit: 163,000 squarings per second, 10-min eval 585.4 s, prove 9.1 s on 12 threads, verify 4.47 ms, 516-byte proof; wrong checkpoint, flipped seed bit and T+1 all rejected; grinding model gains 0 blocks per epoch with the delay against +3.62 at a 30% advantage without it. 3 October 2026, Apple M5 Max, one core. Prototype only: not in the node, not reviewed against chiavdf (O-4.1)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">3</td><td class="claim">The dataset is memory-hard: computing an item costs more than loading it<div class="where">Litepaper Mining and vs RandomX; homepage vs RandomX ("Memory 2 GB, growing")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>58a5a63</code>; igneum-pow 0.1.0 (<code>memhard.rs</code>); <code>proto-metal/MEMHARD.md</code></td><td><code>proto-metal/igneum-bench --inline-dataset</code> against the honest run at 1 GiB and 256 MiB; bench-log "memory-hard dataset"</td><td>Honest 45.2 Mhash/s, inline (never reads the dataset) 9.49 Mhash/s, ratio 0.21, at 1 GiB; 0.10 at 256 MiB. 3 October 2026, Apple M5 Max. Apple only: the shortcut ratio has not run on NVIDIA or AMD (O-1.5). Review round 3 priced a 256 MiB on-die cache chip at about 2.4x, approximate, which the measurement does not answer (ledger M16)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">4</td><td class="claim">The same program produces identical hashes on three GPU vendors, cache and dataset included<div class="where">Litepaper vs RandomX ("Bit-exact on Apple and NVIDIA, measured"), For miners; homepage</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>f2e903e</code>, <code>0f1fdaf</code>; pack <code>proto-cuda/packs/igneum-genesis-mh</code>; igneum-pow 0.1.0</td><td>The 96 test vectors of the pack through <code>proto-metal/igneum-bench</code>, <code>proto-cuda/host.cu</code>, <code>proto-opencl/host.c</code>; batch fingerprint at <code>--batch-log2 24</code>; bench-log entries "RTX 5090, memory-hard dataset", "AMD gfx1036", "RTX 5090 through NVIDIA OpenCL"</td><td>96/96 on Apple Metal (M5 Max), NVIDIA CUDA and NVIDIA OpenCL (RTX 5090, Windows), AMD OpenCL (Ryzen 7 9800X3D integrated gfx1036, 1 compute unit), Apple OpenCL, pocl and two CPU references; batch fingerprint <code>98af644e993239e2</code> over 16.7 million nonces identical on the AMD chip and the 5090. 3 October 2026. The AMD device is an integrated chip; no discrete AMD card and no Intel card has run anything (O-1.15)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">5</td><td class="claim">A CPU verifies one hash in under 10 ms by simulating one warp<div class="where">Litepaper Mining ("about ten milliseconds"), vs RandomX; roadmap gate 2</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>75cac18</code>; igneum-pow 0.1.0 (<code>verify.rs</code>)</td><td><code>cargo test</code> and the crate bench in <code>igneum-pow/</code>; bench-log "igneum-pow: Rust crate bit-exact with proto-metal"</td><td>0.411 to 0.579 ms per 32-lane warp steady, 0.41 to 0.87 ms cold, average of 20, 1 GiB dataset, cache held, one M5 Max performance core; the Swift verifier 0.63 to 1.2 ms. Gate margin about 17x on this core. 3 October 2026. Not measured on a 2019-class laptop core (O-1.14)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">6</td><td class="claim">The hash is bound to the header: one nonce serves one header, and a wrong nonce is rejected<div class="where">Spec 1.6; litepaper Mining (implied by "checks a hash")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>33f7b33</code>, <code>9812466</code>; igneum-pow 0.1.0 (<code>bind.rs</code>, 8 bound vectors, 29 crate tests)</td><td><code>igneum-miner bad-nonce</code> against a devnet node; <code>igneum-pow hash-bound</code> for the 96-nonce job across the 2^32 lane boundary; bench-log "first devnet blocks on the real lottery hash"</td><td>833 blocks accepted by <code>igneum-lottery-v1-bound</code> on 3 nodes, 0 rejections; <code>bad-nonce</code> gave Reject(BlockInvalid); Metal, OpenCL and CUDA (emulated) workers bit-exact with the crate on the lane-boundary job. 3 October 2026, Apple M5 Max</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">7</td><td class="claim">The devnet runs at one block a second<div class="where">Homepage stats ("1 / s"); litepaper Speed; roadmap phase 3</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>9812466</code>, <code>e9328c6</code>; fork worktree <code>vendor/igneum-node-diff</code> branch <code>difficulty</code></td><td>3-node CPU devnet, <code>igneum-miner mine --engine igneum-pow</code>, 660 s; the dual-lane rule's 3-node test network (ports 26800 to 26821); bench-log "first devnet blocks" and "difficulty controller"</td><td>CPU devnet: 1.29 blocks/s over 641 s, 1.03 blocks/s over blocks 610 to 816 after the first retarget, sink identical on 3 nodes at 61 of 64 samples. Kaspa's sampled rule on the overnight devnet did not hold the rate across a hashrate step (5.44 blocks/s for five minutes, 4.7x the schedule for eight minutes). The Igneum dual-lane rule's test network reached 1 block/s within 10% after 132 s, worst gap 7.3 s. 3 October 2026, Apple M5 Max. The phase 3 gate also asks for proofs under 60 s behind the tip; no proof exists (row 15)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">8</td><td class="claim">Blocks are mined by GPUs on Apple and NVIDIA<div class="where">Homepage live strip; journey phase 3 ("GPU miners on three vendors")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>9812466</code>, <code>e9328c6</code>; fork as row 1</td><td>Metal worker <code>proto-metal/igneum-bench --serve</code> driven by <code>igneum-miner --worker</code>, 300 s; the overnight devnet record <code>sim/difficulty/devnet-2026-10-03.csv</code>; bench-log "first devnet blocks" and "difficulty controller"</td><td>Metal: 506 jobs, 5,636 blocks found and accepted, 0 rejected, 0 CPU/GPU mismatches, 28.2 MH/s wall. NVIDIA: the overnight devnet record shows the PC's RTX 5090 mining at 116 MH/s estimated from the blocks, and the finality follower counted eight RTX 5090 identities at 862 to 936 blocks each. 3 October 2026, Apple M5 Max and the Windows PC</td><td class="iv">none yet</td></tr>
<tr data-status="implemented"><td class="n">9</td><td class="claim">Blocks are mined by a GPU on AMD<div class="where">Journey phase 3 ("three vendors")</div></td><td><span class="st st-1">implemented</span></td><td class="mono">repo <code>0f1fdaf</code>; bound kernel <code>kernel_bound.cl</code> in the pack</td><td><code>proto-opencl/host.c --serve</code> on an AMD device against a devnet node</td><td>The bound OpenCL kernel is bit-exact with the crate on Apple OpenCL and the memory-hard pack passes 96/96 on AMD gfx1036, but no block count mined by an AMD device is recorded in the bench log. Until one is, the three-vendor mining claim is two vendors mined plus one vendor verified</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">10</td><td class="claim">Checkpoints lock every 30 s of chain at two thirds of all 30-day weight (raised from 56.7% of total on 4 October 2026), and the floor stops conflicting locks in partitions and eclipses<div class="where">Litepaper Finality, "What Igneum does not claim"; homepage "locked every 30 seconds"</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>bbb264a</code> (simulation), <code>60b1412</code> (fork run); fork "Finality: BLS12-381 vote keys ..." through "Miner: BLS identity ..."; spec 3.10</td><td><code>sim/finality_v2.py</code> (results in <code>sim/results_v2.md</code>); the 4-miner test network <code>igneum-devnet-7</code> with <code>getFinalityCheckpoints</code> on three nodes; bench-log "finality rule V2" and "igneum-node devnet v2"</td><td>Simulation: 0 conflicting locks in every honest partition and eclipse scenario with the floor; without it both sides of a 50/50 split lock after 60 min. Test network: 72 of 72 determined checkpoints locked on all three nodes, lock latency median 0.80 s, p90 1.08 s, 0 conflicting certificates; an equivocating key stripped at index 43 and excluded; with one voter left (39.6% of total) 0 locks for 749 s, then the heal locked 13 checkpoints within 30 s. 3 October 2026, Apple M5 Max, 53 minutes. Not on the live devnet (its miners do not vote yet); the simulation has no DAG; one unexplained stall of all three nodes in the first run, not reproduced</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">11</td><td class="claim">Hashrate that arrived today has almost no vote: ten days of the whole network's hashrate to reach a third of the weight, twenty for two thirds; 51% never reaches two thirds while honest miners stay<div class="where">Litepaper Finality; homepage firsts</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>bbb264a</code>; <code>sim/finality_v2.py</code></td><td>Scenario B of <code>sim/finality_v2.py</code>, seeds 7 and 11</td><td>share(t) = (t/30) x a/(1+a) holds to 0.04 points; a renter equal to the whole honest network (a = 1) crosses 1/3 on day 20 and never reaches 2/3; a = 9 crosses 1/3 on day 11.1 and 2/3 on day 22.2. The ten-day figure is a = infinity, honest miners gone. 3 October 2026, Apple M5 Max. A model with 1,000 Pareto keys and no DAG; the harness scenarios against the real node (1b, 3b, 4b) are stubs</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">12</td><td class="claim">The difficulty rule recovers from a hashrate step within about a minute, where Kaspa's sampled rule never settles<div class="where">Spec 2.3; litepaper Speed (implied); bench page</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>e9328c6</code>; fork worktree <code>vendor/igneum-node-diff</code> branch <code>difficulty</code>; <code>sim/difficulty/sim.py</code></td><td><code>sim/difficulty/sim.py</code> on nine profiles plus the devnet record; the 3-node test network with <code>"difficulty_rule"</code> in the override file; <code>cargo test -p kaspa-consensus --lib difficulty</code> (9 pass); bench-log "difficulty controller"</td><td>Simulator, settled seconds: x50 step 62 (Kaspa 1,542), /50 step 657 (Kaspa 12,296), the devnet's 75x step 79 (Kaspa never). Test network: within 10% of 1 block/s after 132 s warm-up, 214 s on a join, 85 s on a leave. 3 October 2026, Apple M5 Max under load 50 to 98. Monero and LWMA baselines reproduced from memory, approximate; no DAG in the simulator; harness scenarios 2b and 7b are stubs</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">13</td><td class="claim">Every node executes the ordered transactions natively and reaches the same state root<div class="where">Litepaper Proving ("Every node executes ... natively"), Building ("runs on Igneum unchanged")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>f5f8c80</code>; fork worktree <code>vendor/igneum-node-exec</code> branch <code>execution-layer</code>; revm 43.0.3</td><td><code>node tools/evm-smoke/smoke.mjs</code> against a 3-node <code>igneumd --simnet</code>; <code>igneum-exec-diff seq.json</code>; bench-log "execution layer devnet v3"</td><td>87 of 87 viem checks; state roots identical on 3 nodes at chain blocks 0, 56, 74 and 78; 57 executed transactions and 19 skipped copies agree with plain revm, 0 mismatches; balances match receipts to the wei. 3 October 2026, Apple M5 Max. Simnet skips proof of work, the prover is a stub, state is rebuilt from genesis at start, no EVM transaction relay between nodes</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">14</td><td class="claim">Ethereum bytecode runs unchanged, with the documented differences of spec 7.1<div class="where">Homepage Build card; litepaper Building</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">as row 13</td><td><code>tools/evm-smoke/smoke.mjs</code>: deploy via viem, <code>increment</code>, <code>hashLoop</code>, <code>eth_estimateGas</code>, <code>eth_getLogs</code></td><td>Deployment, calls, reverts, logs and gas estimates behave as viem expects; chain id 4463; the prototype pgas table gives 0.0095 to 0.028 pgas per gas, below the design's band before calibration. 3 October 2026, Apple M5 Max. The <code>Prover</code> precompile, proof records and the shard planner are not implemented</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">15</td><td class="claim">Every block is proven, with the proof landing within about a minute at launch<div class="where">Homepage stats ("~60 s to a proof"); litepaper Proving; roadmap phase 3 gate</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 7.2; <code>docs/design/execution-layer.md</code> section 5</td><td>None exists. The phase 2 benchmark standard is <code>docs/benchmarks/proving-e2e.md</code></td><td>No SP1 shard has been proven on any card in this repository (ledger P1, P3). The devnet prover is a stub that signs claims. The 60-second figure is a design target</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">16</td><td class="claim">A 12 GB card proves one shard in about 20 s<div class="where">Litepaper Proving ("The proving budget"); roadmap gate 2</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 5.1 (Target)</td><td>Replaced as a gate by the end-to-end standard in <code>docs/benchmarks/proving-e2e.md</code>: fixed workloads, job-to-accepted-proof latency, no growing backlog</td><td>Unmeasured. A per-shard time can be met by shrinking the shard, so the project no longer uses it as a pass mark</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">1</td><td class="claim">A new mining program every hour, compiled by the miner, with no human in the loop and no pause in mining<div class="where">Homepage hero and "This hour's program"; litepaper Mining</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">igneum-pow 0.2.0; repo <code>b27da39</code>, <code>1292110</code>, <code>100c5d7</code>; fork <code>devnet-v4</code> <code>6457ca95</code></td><td>The live devnet v4: the node announces <code>next_epoch_seed</code> 150 DAA past the seed score, <code>igneum-miner</code> sends <code>prepare</code> to its worker, the worker builds the next program while the current one mines; Metal (<code>proto-metal/igneum-bench</code>), CUDA and OpenCL workers; bench-log "first hourly program swap on the live devnet"</td><td>Epoch boundary at DAA 3,600 (11:05:07 BST, 4 October 2026) crossed live on three vendors: the Mac M5 Max (Metal) compiled the next program in 82 ms, 449 DAA before the boundary, swapped in 0.01 ms, 26.7 MH/s before and after; the RTX 5090 compiled in 1,285 ms, swapped in 0.00 ms, 121.8 before and 123.4 MH/s after; the integrated AMD chip 2.74 MH/s before and after. 0 restarts, 0 rejected blocks, 0 rebuilds. The epoch seed is the epoch block hash; the delay of row 2 is not wired in. At a later boundary (DAA 18,000) one OpenCL worker on PC 2 stayed on the previous epoch after an app reinstall and answered 514 jobs with a seed mismatch; fixed in the miner (<code>3bfe346f</code>, workers emit a <code>need</code> line), the swap time of that forced prepare not measured</td><td class="iv">none yet</td></tr>
<tr data-status="implemented"><td class="n">2</td><td class="claim">The program seed passes through a 10-minute verifiable delay from a certified checkpoint, so nobody can grind the seed<div class="where">Litepaper Mining, vs RandomX ("Closed by a verifiable delay")</div></td><td><span class="st st-1">implemented</span></td><td class="mono">repo <code>792776e</code>; <code>proto-vdf/</code></td><td><code>proto-vdf</code> full 10-minute runs and the tamper cases in <code>proto-vdf/README.md</code>; bench-log "proto-vdf"</td><td>Class group 1024-bit: 163,000 squarings per second, 10-min eval 585.4 s, prove 9.1 s on 12 threads, verify 4.47 ms, 516-byte proof; wrong checkpoint, flipped seed bit and T+1 all rejected; grinding model gains 0 blocks per epoch with the delay against +3.62 at a 30% advantage without it. 3 October 2026, Apple M5 Max, one core. Prototype only: not in the node on 4 October either, not reviewed against chiavdf (O-4.1)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">3</td><td class="claim">The dataset is memory-hard: computing an item costs more than loading it, and every hash does 128 distinct dataset reads<div class="where">Litepaper Mining and vs RandomX; homepage vs RandomX ("Memory 2 GB, growing")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>58a5a63</code> (memory-hard), <code>b27da39</code> (generator 2); igneum-pow 0.2.0 (<code>memhard.rs</code>, <code>generator.rs</code>, <code>accept.rs</code>); spec 01 sections 1.4.2 to 1.4.6; <code>proto-metal/MEMHARD.md</code></td><td><code>proto-metal/igneum-bench --inline-dataset</code> against the honest run at 1 GiB and 256 MiB; <code>igneum-census --gen v2 --warps 64</code> over 20,000 programs; bench-log "memory-hard dataset" and "generator version 2 adopted"</td><td>Honest 45.2 Mhash/s, inline (never reads the dataset) 9.49 Mhash/s, ratio 0.21 at 1 GiB, 0.10 at 256 MiB. 3 October 2026, Apple M5 Max. Generator 2, 4 October 2026: 20,000 programs, 128 static loads on every program, distinct addresses per hash mean 127.9, minimum 120.1; 5.2% of candidates rejected by the acceptance rule. The price of the 128 fresh reads is the hash rate: Apple OpenCL 45.0 MH/s on a version 1 program with 80 distinct loads against 27.5 to 27.9 on version 2; the RTX 5090 229 MH/s on a 104-load version 1 program at 1 GiB (3 October) against 121.8 to 124.2 MH/s mining version 2 on the live devnet (4 October). Apple only for the shortcut ratio (O-1.5); the on-die cache question of ledger M16 is unchanged</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">4</td><td class="claim">The same program produces identical hashes on three GPU vendors, cache and dataset included<div class="where">Litepaper vs RandomX ("Bit-exact on Apple, NVIDIA and AMD, measured"), For miners; homepage</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>f2e903e</code>, <code>0f1fdaf</code> (version 1 packs), <code>b27da39</code> (version 2 packs <code>igneum-genesis-mh</code>, <code>igneum-devnet-v4-epoch0</code>); igneum-pow 0.2.0</td><td>The 96 test vectors of a pack through <code>proto-metal/igneum-bench</code>, <code>proto-cuda/host.cu</code>, <code>proto-opencl/host.c</code>; batch fingerprint at <code>--batch-log2 24</code>; the miner's CPU re-check of every share a GPU worker finds on the devnet; bench-log entries "RTX 5090, memory-hard dataset", "AMD gfx1036", "RTX 5090 through NVIDIA OpenCL", "generator version 2 adopted", "the gfx1036 worker fault"</td><td>Version 1: 96/96 on Apple Metal (M5 Max), NVIDIA CUDA and NVIDIA OpenCL (RTX 5090, Windows), AMD OpenCL (Ryzen 7 9800X3D integrated gfx1036, 1 compute unit), Apple OpenCL, pocl and two CPU references; batch fingerprint <code>98af644e993239e2</code> over 16.7 million nonces identical on the AMD chip and the 5090, 3 October 2026. Version 2: 96/96 on Apple Metal, Apple OpenCL and the CUDA and OpenCL emulators with identical fingerprints; on real NVIDIA and AMD silicon the version 2 vectors have not run as a pack, but both mined accepted blocks on the live devnet with the CPU re-check clean on every share (RTX 5090 at 124.2 MH/s, gfx1036 at 3.3 MH/s), 4 October 2026. The AMD device is an integrated chip; no discrete AMD card and no Intel card has run anything (O-1.15)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">5</td><td class="claim">A CPU verifies one hash in under 10 ms by simulating one warp<div class="where">Litepaper Mining ("about ten milliseconds"), vs RandomX; roadmap gate 2</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>75cac18</code>, <code>b27da39</code>; igneum-pow 0.2.0 (<code>verify.rs</code>)</td><td><code>cargo test</code> and the crate bench in <code>igneum-pow/</code>; bench-log "igneum-pow: Rust crate bit-exact with proto-metal" and "generator version 2 adopted"</td><td>0.411 to 0.579 ms per 32-lane warp steady, 0.41 to 0.87 ms cold, average of 20, 1 GiB dataset, cache held, one M5 Max performance core, 3 October 2026; version 2 units 0.631 ms (average of 20), cold 0.67 to 0.81 ms, 4 October 2026. Gate margin about 16x on this core. Not measured on a 2019-class laptop core (O-1.14)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">6</td><td class="claim">The hash is bound to the header: one nonce serves one header, and a wrong nonce is rejected<div class="where">Spec 1.6; litepaper Mining (implied by "checks a hash")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>33f7b33</code>, <code>9812466</code>, <code>b27da39</code>; igneum-pow 0.2.0 (<code>bind.rs</code>, bound vectors re-cut for version 2, 39 crate tests)</td><td><code>igneum-miner bad-nonce</code> against a devnet node; <code>igneum-pow hash-bound</code> for the 96-nonce job across the 2^32 lane boundary; bench-log "first devnet blocks on the real lottery hash" and "generator version 2 adopted"</td><td>833 blocks accepted by <code>igneum-lottery-v1-bound</code> on 3 nodes, 0 rejections; <code>bad-nonce</code> gave Reject(BlockInvalid); Metal, OpenCL and CUDA (emulated) workers bit-exact with the crate on the lane-boundary job, 3 October 2026, Apple M5 Max. Version 2: the node's engine reports <code>igneum-lottery-v2-bound</code>, 39 of 39 crate tests, and the live devnet v4 accepts its blocks under it, 4 October 2026</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">7</td><td class="claim">The devnet runs at one block a second<div class="where">Homepage stats ("1 / s"); litepaper Speed; roadmap phase 3</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>9812466</code>, <code>e9328c6</code>, <code>8dae48b</code>; fork <code>devnet-v4</code> <code>dc749905</code></td><td>The merged node's 3-node test network (<code>igneum-devnet-880</code>, 960 s); the live devnet v4 record <code>sim/difficulty/records/live-2026-10-04.csv</code>; the 12-node cloud network's arrival logs; bench-log "devnet-v4 integration", "difficulty rule v2", "first devnet blocks"</td><td>Merged node, 4 October 2026, Apple M5 Max: 1,055 blocks in 960 s, 1.03 blocks/s, sink identical on 3 nodes at 31 of 31 samples, 0 rejected. Live devnet v4 the same day: 49 to 81 blocks a minute while two RTX 5090s joined and left (row 12), 1.1 to 1.2 blocks/s in the oscillating window, then within 1.3% per minute with one PC and the Mac. The 12-node cloud network at one block a second: 644 blocks in a 10-minute window. The 3 October CPU devnet: 1.29 blocks/s over 641 s, 1.03 after the first retarget. The phase 3 gate also asks for proofs under 60 s behind the tip; no proof is on the chain (row 15)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">8</td><td class="claim">Blocks are mined by GPUs on Apple and NVIDIA<div class="where">Homepage live strip; journey phase 3 ("GPU miners on three vendors")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>9812466</code>, <code>e9328c6</code>, <code>d7e1f89</code>, <code>2309c8d</code>; fork <code>devnet-v4</code></td><td>Metal worker <code>proto-metal/igneum-bench --serve</code> driven by <code>igneum-miner --worker</code>; the live devnet v4 hash-rate record <code>sim/difficulty/records/live-2026-10-04-hashrate.csv</code> (587 worker STATUS lines by run id); bench-log "first devnet blocks", "devnet v4 cut-over", "difficulty rule v2", "first machine on the Igneum Miner app"</td><td>Metal: 506 jobs, 5,636 blocks found and accepted, 0 rejected, 0 CPU/GPU mismatches, 28.2 MH/s wall, 3 October 2026. Live devnet v4, 4 October 2026: PC 1's RTX 5090 at 122 MH/s with 8 identities, PC 2's at 124 MH/s with 8 identities (117 to 119 MH/s inside the one-click app, 34 accepted blocks in its first minute, CPU re-check OK on every share), the Mac's Metal worker at 26.7 MH/s; 17 vote keys signed the first finality lock (row 10). Two RTX 5090s and one M5 Max; no other NVIDIA model has mined</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">9</td><td class="claim">Blocks are mined by a GPU on AMD<div class="where">Journey phase 3 ("three vendors")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>2c4b30f</code> (generic OpenCL worker, <code>--pack</code>), <code>112acf6</code> (fault guards); bound kernel <code>kernel_bound.cl</code> in the pack</td><td><code>igneum-worker-opencl.exe --pack</code> on PC 2's integrated Radeon against the live devnet v4 through the Windows package; bench-log "the gfx1036 worker fault", "first hourly program swap", "first machine on the Igneum Miner app"</td><td>PC 2's integrated gfx1036 (1 compute unit) mined on the live devnet on 4 October 2026: 8 accepted blocks at 3.3 MH/s over 577 s with the CPU re-check clean, and 2.74 MH/s through the hourly program swap with 0 rejected. At about 600 s the AMD runtime began answering every call with success while running nothing (906 jobs became 56,384 in 30 s, 4.3 GH/s of phantom work); not reproduced on Apple OpenCL in 4,565 jobs with 0 leaked objects; the worker and miner now refuse a job 20x faster than the mean or an unchanged output buffer and restart (<code>112acf6</code>), and the next gfx1036 run names the guard that fires. One integrated chip; no discrete AMD card has run anything</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">10</td><td class="claim">Checkpoints lock every 30 s of chain at two thirds of all 30-day weight, and the floor stops conflicting locks in partitions and eclipses for as long as neither side's own new blocks carry it past two thirds of its window (about 10 days of a 30-day window at a 50/50 split)<div class="where">Litepaper Finality, "What Igneum does not claim"; homepage "locked every 30 seconds"</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>a3a9833</code> (2/3 floor, O-3.15), <code>bbb264a</code> (simulation), <code>c16ccf1</code>; fork <code>devnet-v4</code> <code>6457ca95</code> (<code>FLOOR_NUM / FLOOR_DEN</code> 2/3), <code>da1eb889</code> (F17 by-weight sortition, F1 first-month gate <code>min_daa = window</code>); spec 3.3, 3.3.1, 3.7, 3.9</td><td>The live devnet v4 (<code>getFinalityCheckpoints</code>, <code>tools/observer/observer.mjs</code>, <code>/api/checkpoint</code>); <code>sim/finality_v2.py --floor 1.0</code>, scenarios A to L; the three-node, six-voter partition runs <code>igneum-devnet-921</code> to <code>-923</code>; <code>tools/finality-attacks</code> scenarios 1 to 6 and 8; bench-log "first finality lock on the live devnet", "finality floor 2/3", "finality v2 attack harness", "finality fixes F17 and F1"</td><td>Live: the first lock on the live devnet was checkpoint 242 at 12:03:44 BST on 4 October 2026, two hours after genesis (the window and <code>min_daa</code> are 7,200 DAA), with 77.4% of all weight and of active weight signed by 12 aggregated votes from 17 vote keys; <code>observer.mjs</code> saw it 0.7 s after the miner's own lock line. By 13:21 UTC the observer held 280 certificates, indices 241 to 522 (DAA 7,229 to 17,982), 17 to 27 voters, no index with two hashes. Test networks, 4 October 2026, Apple M5 Max: a 4/2 split locked on the 4 side (67.9%) 2 to 8 s after the cut and never on the 2 side, 0 conflicts; a 3/3 split locked on neither side for 150 s with 0 conflicts, where the 3 October floor (56.7%) would have locked both sides at 76 and 106 s; a 3/3 split held for 205 s crossed the bound (predicted W / (3R) = 200 s) and the two sides certified different checkpoints, 26 conflicting certificates, a finality fork the heal did not undo (ledger F21; the operator override of F5 is unwritten). Simulator with the 2/3 floor: 0 conflicts up to a 33% equivocator (34% splits a 50/50 partition), silent weight pauses locks from 34%, a 50/50 partition locks alone from day 10.1. Harness: equivocating keys stripped on every node, Sybil dust at zero weight, a pulsed miner's weight equal to its block share (ratio 0.96 to 1.0), the first-month gate stops a young window locking under one key. Not demonstrated: certificate injection on the wire, an eclipse with a private fork, the 2-hour presence window at mainnet length</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">11</td><td class="claim">Hashrate that arrived today has almost no vote: ten days of the whole network's hashrate to reach a third of the weight, twenty for two thirds; 51% never reaches two thirds while honest miners stay<div class="where">Litepaper Finality; homepage firsts</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>bbb264a</code>; <code>sim/finality_v2.py</code></td><td>Scenario B of <code>sim/finality_v2.py</code>, seeds 7 and 11</td><td>share(t) = (t/30) x a/(1+a) holds to 0.04 points; a renter equal to the whole honest network (a = 1) crosses 1/3 on day 20 and never reaches 2/3; a = 9 crosses 1/3 on day 11.1 and 2/3 on day 22.2. The ten-day figure is a = infinity, honest miners gone. 3 October 2026, Apple M5 Max. A model with 1,000 Pareto keys and no DAG; the live devnet's window is two hours old, so the claim has no live measurement yet</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">12</td><td class="claim">The difficulty rule recovers from a hashrate step within minutes, where Kaspa's sampled rule never settles. A step inside an epoch set the rule oscillating on the live devnet on 4 October 2026; rule v2 removes it in the simulator and on a test network and is built but not yet rolled out<div class="where">Spec 2.3; litepaper Speed (implied); bench page</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>e9328c6</code>, <code>abb5a5d</code> (attacks), <code>67bf226</code> (rule v2); fork <code>difficulty</code> branch (timestamp fix) and <code>devnet-v4</code> <code>a21ff239</code> (<code>difficulty_v2_activation_daa</code>, <code>REF_WINDOW_V2 = 600</code>); <code>sim/difficulty/sim.py --live</code></td><td>The live record <code>sim/difficulty/records/live-2026-10-04.csv</code> (8,090 headers, <code>pull_live.py</code>) and the hash-rate record beside it; <code>sim/difficulty/sim.py</code> on the synthetic set and the DAG replay; <code>sim/difficulty/attacks/attacks.py</code>; <code>sim/difficulty/testnet_v2.py</code> (3 nodes, activation at DAA 900); <code>cargo test --release -p kaspa-consensus --lib difficulty</code> (15 pass); bench-log "difficulty controller", "difficulty rule under attack", "timestamp attack fixed", "difficulty rule v2"</td><td>Live devnet v4, 4 October 2026 (UTC): a second RTX 5090 joining 7 minutes into an epoch (about 152 to 280 MH/s) hardened the difficulty 70M to 144M in 90 s and then oscillated for 40 minutes, 102M to 164M, peaks of 1.33x every 132 to 150 chain blocks, 54 to 81 blocks a minute, against a true level of 139M; that card leaving for 4 minutes eased 116M to 67M and back to 106M; the epoch boundary with both PCs restarting took 152M to 77M in 3 minutes, after which the rule held within 1.3% per minute with no flips. Cause: the reference lane covered the whole epoch, so a mid-epoch step polluted it for the hour and the 25% trigger flipped on the short lane's noise. The DAG replay reproduces the record (std of log difficulty 0.115 against 0.134, 4.3 peaks against 4). Rule v2 (reference window 600 DAA) on the replay: std 0.026, 0 flips, mean 142.6M against 139M true; on a 3-node test network the v2 nodes eased a leave with no peak and held a rejoin within 3% after 60 s, and a node without the activation height forked off at it as designed. Rollout pending: every devnet node needs the same activation height in its override file (<code>docs/plans/difficulty-v2-rollout-devnet.md</code>), the cloud network first. Timestamp forging (ledger M23) fixed the same day: a 50% forger drifts the rate under 1.1% where the 3 October rule gave it a 9.9x difficulty. Simulator, settled seconds: x50 step 62 to 66 (Kaspa 1,542), /50 step 657 to 753 (Kaspa 12,296). Apple M5 Max under load 7 to 442; the DAG model is fitted on one scale; the pool hopper's 0.7-point excess over Kaspa's rule stays open</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">13</td><td class="claim">Every node executes the ordered transactions natively and reaches the same state root<div class="where">Litepaper Proving ("Every node executes ... natively"), Building ("runs on Igneum unchanged")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>f5f8c80</code>, <code>8dae48b</code>; fork <code>devnet-v4</code> <code>dc749905</code>; revm 43.0.3</td><td><code>node tools/evm-smoke/smoke.mjs</code> against a 3-node <code>igneumd</code>; <code>igneum-exec-diff seq.json</code>; bench-log "execution layer devnet v3" and "devnet-v4 integration"</td><td>Simnet, 3 October 2026: 87 of 87 viem checks, state roots identical on 3 nodes at four heights, 57 executed and 19 skipped transactions agree with plain revm, 0 mismatches. Merged node on real proof of work, 4 October 2026: 84 of 85 checks (the miss needs parallel blocks the network did not produce in 36 s), 59 transfers in 10 chain blocks, state roots identical on 3 nodes, <code>igneum-exec-diff</code> 0 mismatches over 59 transactions; the live devnet v4 runs this execution layer. Apple M5 Max. The prover is a stub; state is rebuilt from genesis at start; no EVM transaction relay between nodes</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">14</td><td class="claim">Ethereum bytecode runs unchanged, with the documented differences of spec 7.1<div class="where">Homepage Build card; litepaper Building</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">as row 13; fixes <code>F-exec-A</code>, <code>F-exec-B</code> (spec 7.5)</td><td><code>tools/evm-smoke/smoke.mjs</code>: deploy via viem, <code>increment</code>, <code>hashLoop</code>, <code>eth_estimateGas</code>, <code>eth_getLogs</code>; <code>tools/exec-attacks</code> scenarios 1 and 3; bench-log "execution layer attack fixes"</td><td>Deployment, calls, reverts, logs and gas estimates behave as viem expects; chain id 4463; the prototype pgas table gives 0.0095 to 0.028 pgas per gas, below the design's band before calibration, 3 October 2026. 4 October 2026: a transaction that would cross the block's proving budget is refused by the mempool and, if forced in, aborted and charged with its nonce advanced (25 of 25 checks; 30 of 30 malformed cases). Apple M5 Max. The <code>Prover</code> precompile, proof records and the shard planner are not in the node</td><td class="iv">none yet</td></tr>
<tr data-status="implemented"><td class="n">15</td><td class="claim">Every block is proven, with the proof landing within about a minute at launch<div class="where">Homepage stats ("~60 s to a proof"); litepaper Proving; roadmap phase 3 gate</div></td><td><span class="st st-1">implemented</span></td><td class="mono">repo <code>d7e1f89</code> (GPU proof), <code>e01a3cc</code>, <code>292e800</code>, <code>eedd136</code> (<code>proving/igneum-prove</code>: shard cutter, MPT witnesses, shard and aggregator guests); SP1 6.8.1; spec 7.2, 7.6</td><td><code>proving/windows-wsl2</code> (SETUP-PROVER, PROVE-BLOCK) on the RTX 5090; <code>igneum-prove-host --mode block</code> on <code>proving/fixtures/</code>; bench-log "proving v0 on the RTX 5090" and "proving: devnet v4 shards"</td><td>First GPU proof of an Igneum block, 4 October 2026, RTX 5090 (WSL2, SP1 cuda, mining paused): fixture <code>block-78-increment</code> (2 transactions), core proof 1.4 s (7.3 MB, verify 0.221 s), compressed proof 2.7 s (1.27 MB, verify 0.038 s), post-state and receipts roots identical to the node's; 15.7x and 20.6x faster than a loaded M5 Max CPU. The same day on that CPU (load 38 to 47): a three-shard block proved shard by shard and aggregated by recursion, 19 min (1,139 s) end to end, 245 to 337 s per compressed shard proof, every proof verified. What is not there: no proof is produced, carried or checked on the chain (the devnet prover is a stub that signs claims), the proving pool pays nobody (row 21), the block proven is far below one shard, and the 60-second figure remains a design target; the pass mark is the standard in <code>docs/benchmarks/proving-e2e.md</code>. Two host defects (an abort after the upload, a 10-minute idle wait) fixed, to be confirmed on the PC (ledger P20)</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">16</td><td class="claim">A 12 GB card proves one shard in about 20 s<div class="where">Litepaper Proving ("The proving budget"); roadmap gate 2</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 5.1 (Target), 7.6 (<code>S_p</code> provisional, 7,500,000 pgas = <code>B_p</code> / 4)</td><td><code>PROVE-SHARD.bat</code> on the RTX 5090 (pending); the end-to-end standard in <code>docs/benchmarks/proving-e2e.md</code>; bench-log "proving: devnet v4 shards"</td><td>Unmeasured on any GPU. A shard at the provisional <code>S_p</code> is 60 M SP1 cycles on the prototype pgas table (9 cycles per pgas; the modexp entry about 100x its SP1 cost), executed in 4.6 to 7.7 s on the Mac CPU and not yet proven; the GPU row of the bench-log table is empty until the PC runs it, 4 October 2026. A per-shard time can be met by shrinking the shard, so the project does not use it as a pass mark</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">17</td><td class="claim">The chip resistance target: a chip gains under 2x over a GPU<div class="where">Litepaper Mining, "What Igneum does not claim"; homepage "no chip can be built for it"</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 0.2 (Target); O-1.17</td><td>Public benchmark with a leaderboard by card model and a standing bounty, January 2027 (O-1.17); the on-die-SRAM test on the RTX 5090 (R3.5)</td><td>A target, not a measurement. Review round 3 priced a recompute chip with the 256 MiB cache on die at about 2.4x, approximate, before the usual chip-versus-GPU integer gain; the design answer (cache larger than any die) is open (spec 1.16)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">18</td><td class="claim">The chip resistance measurements: the program is random-access bound, not bandwidth bound, and sits beyond a card's on-chip cache<div class="where">Litepaper Mining ("bound by memory bandwidth", to be corrected), vs RandomX "Measured so far"</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>aba248d</code>, <code>f2a1a64</code>, <code>4b95c5e</code></td><td>RTX 5090 dataset sweep 4 MiB to 1 GiB with <code>proto-cuda/host.cu</code>; bench-log "RTX 5090 first run" and "dataset sweep"</td><td>At 1 GiB: 228.1 Mhash/s, 23.7 G random loads/s, 94.9 GB/s useful against a 1,638 GB/s dataset fill; inside the 96 MiB L2 (4 and 64 MiB) 1,340 to 1,353 Mhash/s, about 5.8x faster; 104 against 128 loads per hash gives 228 against 185 Mhash/s, proportional. 3 October 2026, RTX 5090, Windows, CUDA 12.8. Prototype dataset 1 GiB against 2 GB at genesis; a pure random-read microbenchmark (R3 chip designer, attack 2) has not run</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">19</td><td class="claim">The lottery hash is sound as a hash: uniform output, deterministic, no out-of-bounds read, fuzzed<div class="where">Litepaper vs RandomX ("Every number above is measured and logged")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>c52307e</code>, <code>58a5a63</code>; <code>proto-metal/TESTS.md</code></td><td><code>proto-metal/igneum-bench --fuzz --edge --stats --determinism --memcheck</code>; bench-log "hardening tests" and the re-run on the memory-hard dataset</td><td>10,200 random programs, 1,305,600 hashes, 0 mismatches; 14 of 14 edge cases; bit frequency within 2.90 sigma, avalanche mean 31.99 to 32.04 of 32; deterministic fingerprint across 5 runs; every dataset read masked. 3 October 2026, Apple M5 Max. Statistics are not a security proof; the weak-program census (O-1.3) and the seed derivation review (O-1.4) are open; the fuzz set has run on Metal and the CPU only</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">18</td><td class="claim">The chip resistance measurements: the program is random-access bound, not bandwidth bound, and sits beyond a card's on-chip cache<div class="where">Litepaper Mining ("bound by memory bandwidth", to be corrected), vs RandomX "Measured so far"</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>aba248d</code>, <code>f2a1a64</code>, <code>4b95c5e</code></td><td>RTX 5090 dataset sweep 4 MiB to 1 GiB with <code>proto-cuda/host.cu</code>; bench-log "RTX 5090 first run" and "dataset sweep"</td><td>At 1 GiB: 228.1 Mhash/s, 23.7 G random loads/s, 94.9 GB/s useful against a 1,638 GB/s dataset fill; inside the 96 MiB L2 (4 and 64 MiB) 1,340 to 1,353 Mhash/s, about 5.8x faster; 104 against 128 loads per hash gives 228 against 185 Mhash/s, proportional. 3 October 2026, RTX 5090, Windows, CUDA 12.8, version 1 programs. Prototype dataset 1 GiB against 2 GB at genesis; a pure random-read microbenchmark (R3 chip designer, attack 2) has not run; the sweep has not been repeated on version 2</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">19</td><td class="claim">The lottery hash is sound as a hash: uniform output, deterministic, no out-of-bounds read, fuzzed<div class="where">Litepaper vs RandomX ("Every number above is measured and logged")</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>c52307e</code>, <code>58a5a63</code>, <code>b27da39</code>; <code>proto-metal/TESTS.md</code></td><td><code>proto-metal/igneum-bench --fuzz --edge --stats --determinism --memcheck</code>; <code>--fuzz 2000</code> on the version 2 generator; <code>igneum-census</code>; bench-log "hardening tests", the re-run on the memory-hard dataset, "generator version 2 adopted"</td><td>Version 1: 10,200 random programs, 1,305,600 hashes, 0 mismatches; 14 of 14 edge cases; bit frequency within 2.90 sigma, avalanche mean 31.99 to 32.04 of 32; deterministic fingerprint across 5 runs; every dataset read masked, 3 October 2026. Version 2, 4 October 2026: 2,000 random programs through the Metal cross-check, 8,000 warps, 0 mismatches, 128 loads per hash on every program; 20,000-program census, 5.2% rejected (4.1% static, 1.1% dynamic). Apple M5 Max. Statistics are not a security proof; the edge, stats and memcheck sections were not re-run on version 2 (they do not depend on the generator); the seed derivation review (O-1.4) is open; the fuzz set has run on Metal and the CPU only</td><td class="iv">none yet</td></tr>
<tr data-status="implemented"><td class="n">20</td><td class="claim">No premine, no pre-sale, no allocation: every coin is minted by the schedule and every coin goes to the block producer (80%) and the proving pool (20%)<div class="where">Homepage stats and Economics tiles; litepaper Supply, Economics</div></td><td><span class="st st-1">implemented</span></td><td class="mono">repo <code>6ac80a3</code>; fork "igneum-node devnet v0"; <code>consensus/core/src/igneum.rs</code>, <code>coinbase.rs</code></td><td><code>cargo test -p kaspa-consensus-core igneum</code> (8 pass: subsidy table, ramp, split, cap) and <code>cargo test -p kaspa-consensus coinbase</code> (8 pass); <code>igneum-miner inspect 40</code>; bench-log "igneum-node devnet v0"</td><td>Coinbases on the devnet: 80/20 exact on 39 of 39 single-payee blocks, the 20% to the <code>igneum-proving-pool-v0</code> output; the per-second schedule sums to under the 4,000,000,000 cap by less than 100 coins; 3,168,808,781 units per DAA second in years 0 to 2, halving at 63,115,200 DAA s. 3 October 2026, Apple M5 Max. The devnet genesis carries no allocation; the mainnet genesis does not exist yet, so the claim is about the code and the stated rule, not a launch that has happened</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">21</td><td class="claim">The proving pool's 20% reaches shard provers and aggregators<div class="where">Litepaper Economics; homepage "20% provers"</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 5.3</td><td>None. The pool output exists (row 20); the payout from it against proof records is unwritten</td><td>The escrow accumulated on the simnet (92.55 IGN at the end of the v3 run) and nothing can draw it. Rule decided: per block, divided among shards by consensus proving cost, sortition to 8 provers for 10 s then open (spec 7.2)</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">21</td><td class="claim">The proving pool's 20% reaches shard provers and aggregators<div class="where">Litepaper Economics; homepage "20% provers"</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 5.3; <code>proving/igneum-prove</code> carries the prover's payout address in every shard proof (ledger P12)</td><td>None. The pool output exists (row 20); the payout from it against proof records is unwritten</td><td>The escrow accumulated on the simnet (92.55 IGN at the end of the v3 run) and nothing can draw it. Rule decided: per block, divided among shards by consensus proving cost, sortition to 8 provers for 10 s then open (spec 7.2). The economy model of 4 October 2026 (<code>sim/economy</code>, 1,000 operators, 30 days) kept every block proven within 60 s under six stress scenarios; a model, not hardware</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">22</td><td class="claim">The base fee is burned in full and the priority fee splits 80% to the miner and provers, 20% to the apps whose code ran<div class="where">Homepage Economics caption and Build card; litepaper "Where fees go"</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>f5f8c80</code>; fork worktree <code>vendor/igneum-node-exec</code></td><td><code>tools/evm-smoke/smoke.mjs</code> receipt checks; bench-log "execution layer devnet v3"</td><td>Transfer receipt: <code>burnedProvingFee</code> 200 gwei, <code>minerTip</code> 16,800 gwei (80%), unregistered developer share 4,200 gwei burned; contract call: 80% to the miner, 20% credited to the payee the constructor registered, balance delta equal. 3 October 2026, Apple M5 Max simnet. The provers' part of the 80% is not split out (no provers exist); the base fee stayed at the 1 gwei floor throughout</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">23</td><td class="claim">No fee to any team, foundation or fund; 0 admin keys in consensus<div class="where">Homepage Economics tiles and caption; litepaper "No fund, no foundation" and Governance</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 5.5, 5.6 (decided 3 October 2026); spec 08</td><td>Reading: no coinbase output, fee route or consensus key in the fork names any party (<code>coinbase.rs</code>, <code>docs/fork-divergence.md</code>)</td><td>The emission code has two outputs (row 20) and the fee code has three routes (row 22), none to a team. The 1% fee of the official client is a client setting, not a protocol rule, and is not implemented (no client exists). The release key of spec 08 signs client updates and holds no consensus power; its custody policy is open (O-8.1)</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">23</td><td class="claim">No fee to any team, foundation or fund; 0 admin keys in consensus<div class="where">Homepage Economics tiles and caption; litepaper "No fund, no foundation" and Governance</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 5.5, 5.6 (decided 3 October 2026); spec 08</td><td>Reading: no coinbase output, fee route or consensus key in the fork names any party (<code>coinbase.rs</code>, <code>docs/fork-divergence.md</code>)</td><td>The emission code has two outputs (row 20) and the fee code has three routes (row 22), none to a team. The 1% fee of the official client is a client setting, not a protocol rule, and is not implemented. The release key of spec 08 signs client updates (the Igneum Miner app's over-the-air manifest since 4 October 2026, Ed25519) and holds no consensus power; its custody policy is open (O-8.1)</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">24</td><td class="claim">External proving jobs pay 90% to the provers who delivered and burn 10%, once settled in IGN<div class="where">Homepage "IGN burned from jobs, phase two"; litepaper Proving and Economics</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 5.4</td><td>None. Needs the proof bridge (spec 7.3, phase two) and the settlement switch (O-5.2)</td><td>At launch jobs are paid on the customer's chain in the customer's currency and nothing is burned (ledger P10). No job market code exists</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">25</td><td class="claim">The 4 billion cap, halving every two years, with a 30-day ramp from 10%<div class="where">Homepage "4B IGN hard cap"; litepaper Supply and the emission chart</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>6ac80a3</code>; fork <code>consensus/core/src/igneum.rs</code></td><td><code>cargo test -p kaspa-consensus-core igneum</code>; bench-log "igneum-node devnet v0"</td><td>Ramp day 0 paid 10.03% of the full rate (317,767,704 units at DAA 806); the schedule table and the cap assert in the crate's own tests. 3 October 2026, Apple M5 Max. Base unit (8 or 18 decimals) is open (O-2.6); the spec was changed to follow the code's 365.25-day year (ledger E9) and a test that reads the published numbers back is still owed</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">26</td><td class="claim">A phone or browser verifies the chain from a locked checkpoint, at about 3.44 MB per day in checkpoint mode<div class="where">Homepage "Browser checks Igneum" card (preview, commit <code>f874f80</code>); litepaper Building ("Light clients"), firsts row 6</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 10 (10.5 bytes per day: 3.44 MB at 1,000 voters, 6.68 MB at 10,000, derived, approximate); repo <code>f874f80</code> for the browser card</td><td>None for the byte figure; <code>site/verify/</code> for the card. BLS verification on a phone and in WebAssembly is O-10.3; the full-header mode on a phone is O-10.4</td><td>The homepage card verifies the latest certified checkpoint's BLS certificate in the tab against a voter list from the node (light client v0, 3 October 2026). The byte figure is arithmetic on designed sizes (header 400 bytes, proof 400 bytes), measured nowhere; the proof the card would check does not exist (row 15)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">27</td><td class="claim">The node survives malformed input, floods, withholding, partitions and eclipses<div class="where">Litepaper Speed ("GHOSTDAG, the BlockDAG consensus proven on Kaspa"); spec 2</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>394030c</code>; fork worktree <code>vendor/igneum-node-harness</code>; <code>tools/harness/</code></td><td><code>tools/harness/</code> scenario runner against a private <code>igneumd</code> test network (ports 27200+); bench-log "consensus attack harness"</td><td>63 malformed cases, node up on every one; timestamp bounds exact; withholding at 10%, 25%, 33% and 45% released every 5 blocks within 2 sigma of share; partitions of 120 s to 3,700 s healed to one chain in 10 s; eclipse victims rejoined in 10 s; 50x template, submit and mempool floods left template p95 under 4 ms. One FAIL: a 45% withholder releasing every 20 blocks took 50.7% of blues (bound 47.4%). 3 October 2026, Apple M5 Max, load 50 to 61. Finality and difficulty scenarios are stubs until those branches merge</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">28</td><td class="claim">Headers are validated cheaply before the lottery engine runs, so forged timestamps cannot force 256 MiB cache builds<div class="where">Spec 2.4; ledger M15</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>0953ec7</code>; fork worktree <code>vendor/igneum-node-r3</code> branch <code>r3-fixes</code> at <code>5166ee26</code></td><td><code>measure_m15_attack_before_and_after</code> (ignored test, release, <code>--features igneum-pow</code>); kaspa-pow 8, header_processor 1, p2p <code>pow_guard</code> 2 tests</td><td>50 forged headers: before, 50 cold builds in 10,595 ms and the live day evicted; after, 0 builds, all 50 rejected in 14 ms. 3 October 2026, Apple M5 Max under load 60 to 110. Measured through the validate path with <code>skip_proof_of_work</code>, not the daemon RPC; not merged into the main fork branch</td><td class="iv">none yet</td></tr>
<tr data-status="designed"><td class="n">26</td><td class="claim">A phone or browser verifies the chain from a locked checkpoint, at about 3.44 MB per day in checkpoint mode<div class="where">Homepage "Browser checks Igneum" card; litepaper Building ("Light clients"), firsts row 6</div></td><td><span class="st st-0">designed</span></td><td class="mono">spec 10 (10.5 bytes per day: 3.44 MB at 1,000 voters, 6.68 MB at 10,000, derived, approximate); repo <code>f874f80</code> for the browser card; <code>site/api/checkpoint.mjs</code></td><td>None for the byte figure; <code>site/verify/</code> for the card against <code>/api/checkpoint</code>. BLS verification on a phone and in WebAssembly is O-10.3; the full-header mode on a phone is O-10.4</td><td>Since 12:03 BST on 4 October 2026 the homepage card verifies the live devnet's own certificates in the tab (index 522 with 27 voters at 13:42 UTC), BLS aggregate against the voter list the node serves, light client v0; before that it verified the 3 October test network's. The byte figure is arithmetic on designed sizes (header 400 bytes, proof 400 bytes), measured nowhere; the execution proof the card would also check is not on the chain (row 15)</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">27</td><td class="claim">The node survives malformed input, floods, withholding, partitions and eclipses<div class="where">Litepaper Speed ("GHOSTDAG, the BlockDAG consensus proven on Kaspa"); spec 2</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>394030c</code>, <code>8dae48b</code>, <code>6b5bd92</code>; fork worktree <code>vendor/igneum-node-harness</code> and <code>devnet-v4</code>; <code>tools/harness/</code>; <code>infra/cloud-devnet/experiments/partition.sh</code></td><td><code>tools/harness/</code> against a private <code>igneumd</code> test network; the merged node's harness scenarios 2 and 5; the cloud network's 10-minute partition of Singapore (<code>results/2026-10-04/partition-sin-20261004-110906/partition.md</code>); bench-log "consensus attack harness", "devnet-v4 integration"</td><td>3 October 2026, Apple M5 Max: 63 malformed cases, node up on every one; withholding at 10% to 45% within 2 sigma of share; partitions of 120 s to 3,700 s healed to one chain in 10 s; eclipse victims rejoined in 10 s; 50x floods left template p95 under 4 ms; one FAIL, a 45% withholder releasing every 20 blocks took 50.7% of blues (bound 47.4%). Merged node, 4 October 2026: 63 cases, node up, 0 cache builds; the 10 s timestamp floor and future bound exact. Cloud network, 4 October 2026: 12 nodes in five locations on their own chain, Singapore cut off by iptables for 10 minutes; the two minority nodes adopted the majority chain 10 and 14 s after the heal with reorgs of 445 and 516 blocks, the majority's deepest reorg was 2 blocks, 0 conflicting locks (none were possible: the weight window stood at DAA 3,030 of 7,200). CPU miners only; the finality rules under partition are row 10</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">28</td><td class="claim">Headers are validated cheaply before the lottery engine runs, so forged timestamps cannot force 256 MiB cache builds<div class="where">Spec 2.4; ledger M15</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>0953ec7</code>, <code>8dae48b</code>; fork worktree <code>vendor/igneum-node-r3</code> branch <code>r3-fixes</code> at <code>5166ee26</code>, merged into <code>devnet-v4</code></td><td><code>measure_m15_attack_before_and_after</code> (ignored test, release, <code>--features igneum-pow</code>); kaspa-pow 8, header_processor 1, p2p <code>pow_guard</code> 2 tests; harness scenario 5 on the merged node</td><td>50 forged headers: before, 50 cold builds in 10,595 ms and the live day evicted; after, 0 builds, all 50 rejected in 14 ms, 3 October 2026, Apple M5 Max under load 60 to 110. Merged node, 4 October 2026: 63 harness cases with 0 cache builds (the node log shows one build, the honest day) and the M15 p2p cases disconnected by the strike guard; the live devnet v4 runs it. Measured through the validate path with <code>skip_proof_of_work</code>, not the daemon RPC</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">29</td><td class="claim">Blocks reach every node well inside GHOSTDAG's delay bound across continents<div class="where">Litepaper Speed (GHOSTDAG at one block a second); spec 03 C1 (lock latency); <code>infra/cloud-devnet/README.md</code></div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>6b5bd92</code>; <code>infra/cloud-devnet/experiments/latency.sh</code>, <code>analyze.py</code>; the Linux cross-build <code>infra/cross/build-linux.sh</code></td><td>12 <code>igneumd</code> nodes on Hetzner VMs in Helsinki, Falkenstein, Ashburn, Hillsboro and Singapore (own chain <code>igneum-devnet-20</code>, one CPU trickle miner each), a ping matrix, then 10 minutes of per-node arrival logs joined on block hash; <code>results/2026-10-04/latency/propagation.md</code> and <code>rtt-by-region.md</code></td><td>644 blocks in the window, 642 seen by at least 80% of nodes; arrival at a node minus the first arrival anywhere: p50 343 ms, p90 497 ms, p99 666 ms, max 2,313 ms; by region p50 239 ms (Falkenstein) to 413 ms (Singapore), p90 455 to 632 ms; inter-region RTT 35 ms (Helsinki to Falkenstein) to 289 ms (Ashburn to Singapore); first arrival minus header time median 490 ms. 4 October 2026. The network is the project's own: 12 nodes not 20 (a new account's limits), CPU hash rate only, clocks by chrony, one evening of data; the 5 s bound behind GHOSTDAG k is a design parameter this run did not challenge</td><td class="iv">none yet</td></tr>
<tr data-status="tested by the team"><td class="n">30</td><td class="claim">One click: install, press start, the card mines; the app looks after its node<div class="where">Homepage Mine section ("One click: install, press start"); litepaper "One click, for everyone else"; journey phase 5</div></td><td><span class="st st-2">tested by the team</span></td><td class="mono">repo <code>3bb50d6</code>, <code>2c4b30f</code>, <code>6461540</code> (package 0.3.0: prebuilt NVRTC CUDA worker and generic OpenCL worker, driver only), <code>a1a33cb</code>, <code>7c794df</code>, <code>0d4498e</code>, <code>6c083db</code> (Igneum Miner 0.3.0), <code>78903cd</code> (0.3.1, over-the-air updates)</td><td><code>Igneum-Miner-Setup-0.3.0.exe</code> (runner-built, unsigned) on a Windows PC with an RTX 5090 and no toolchain; <code>proto-cuda/nvrtc/emu/serve-check.sh</code> on the Mac; <code>proto-cuda/windows-app/TEST.md</code>; bench-log "one-click Windows workers", "first machine on the Igneum Miner app", "a node 60 s behind the clock is silently dead", "the gfx1036 worker fault"</td><td>One machine so far, PC 2, 4 October 2026. The NVRTC worker compiled the pack on the card with no toolchain installed and mined at 124.2 MH/s, equal to the nvcc-built worker, 0 rejected, CPU re-check clean; inside the app 117 to 119 MH/s with 34 accepted blocks in the first minute, the integrated AMD chip at 3.3 MH/s beside it (row 9). Two defects found by the install, both fixed the same hour: a clock 62 s slow after a power cut made the node reject every relayed block for 12 minutes with no visible reason (the app now reads the skew from the node's warnings, the block timestamps over the EVM RPC and an HTTPS Date header, warns over 5 s and blocks Start over 10 s, with a one-click clock sync; checked on the Mac with a fake 60 s skew; a one-line node warning is filed), and the node card said "syncing" while the miner was already accepted. The Mac could only emulate the NVIDIA path (17 of 17 sampled hashes) and the AMD path on Apple OpenCL (15 of 15). Over-the-air updates were dry-run on a private devnet (0.3.0 to 0.3.1 and back), not on a user's machine. The installer is unsigned (SmartScreen "run anyway"). The live devnet's three GPU machines (two PCs and the Mac) run the same workers; one of them through the app</td><td class="iv">none yet</td></tr>
</tbody></table></div>
<p class="note">Click a column heading to sort; click again to reverse. Versions: <code>igneum-pow</code> is the Rust crate at version 0.1.0; repo commits are this repository's; fork commits are the node fork and its worktrees, named by message as the engineering log names them. Source of every number: the <a href="/bench">engineering log</a>. The source of this page is <code>docs/evidence.md</code> in the repository.</p>
<p class="note">Click a column heading to sort; click again to reverse. Versions: <code>igneum-pow</code> is the Rust crate at version 0.2.0 (generator version 2, 4 October 2026); repo commits are this repository's; fork commits are the node fork and its worktrees, named by message as the engineering log names them. Source of every number: the <a href="/bench">engineering log</a>. The source of this page is <code>docs/evidence.md</code> in the repository.</p>
<h2>What would move a row</h2>
<div class="tbl small"><table><thead><tr><th>From</th><th>To</th><th>What it takes</th></tr></thead><tbody>
<tr><td>designed</td><td>implemented</td><td>Code in this repository with test vectors that pass</td></tr>
@ -121,7 +123,7 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c
<tr><td>reproduced externally</td><td>reviewed independently</td><td>A named reviewer's published finding on that version. Funding for review is <code>docs/plans/funding.md</code></td></tr>
<tr><td>any</td><td>the row's status falls back</td><td>A new version of the code or rule the row names</td></tr>
</tbody></table></div>
<footer>© 2026 Igneum. Statuses are honest as of 3 October 2026 and change only through this page. Nothing on this page is an offer to sell anything.</footer>
<footer>© 2026 Igneum. Statuses are honest as of 4 October 2026 and change only through this page. Nothing on this page is an offer to sell anything.</footer>
</div>
<script>
(function(){

View file

@ -278,7 +278,7 @@ footer .wrap{padding-block:48px 32px}
</div>
</div>
<div class="verify-grid">
<div class="cell"><div class="k">BLOCK PROOF</div><div class="v" data-lc="proof" title="Execution proofs do not exist yet">not yet</div></div>
<div class="cell"><div class="k">BLOCK PROOF</div><div class="v" data-lc="proof" title="No execution proof is on the chain yet. The first off-chain GPU proof of a block was 4 October 2026">not yet</div></div>
<div class="cell"><div class="k">CHECKPOINT</div><div class="v" data-lc="checkpoint">checking</div></div>
<div class="cell"><div class="k">PROOF SYSTEM</div><div class="v" data-lc="system">BLS aggregate, version 1</div></div>
<div class="cell"><div class="k">VERIFIED IN THIS TAB</div><div class="v" style="color:var(--molten)" data-lc="tab">checking</div></div>
@ -300,7 +300,7 @@ footer .wrap{padding-block:48px 32px}
<div class="stats">
<div class="wrap">
<div class="stat reveal"><div class="v" data-count="1" data-suffix=" / s">1 / s</div><div class="k">blocks, rising to 10</div></div>
<div class="stat reveal"><div class="v">~60 s</div><div class="k">to a proof at launch</div></div>
<div class="stat reveal"><div class="v">~60 s</div><div class="k">to a proof at launch, the target. First GPU proof of a block: 1.4 s on an RTX 5090, 4 Oct 2026</div></div>
<div class="stat reveal"><div class="v" data-count="0">0</div><div class="k">premine</div></div>
<div class="stat reveal"><div class="v" data-count="4" data-suffix="B">4B</div><div class="k">IGN hard cap, ever</div></div>
<div class="stat reveal"><div class="v" data-count="100" data-suffix="%">100%</div><div class="k">to miners and provers</div></div>
@ -311,7 +311,7 @@ footer .wrap{padding-block:48px 32px}
<div class="wrap">
<div class="sec-head reveal">
<h2>Watch the chain prove itself</h2>
<p>Blocks every second, proven in shards, locked every 30 seconds.</p>
<p>Blocks every second, proven in shards, locked every 30 seconds. First live lock 4 Oct 2026: checkpoint 242, 77.4% of all weight, two hours after genesis.</p>
</div>
<div class="card viz reveal" aria-label="Animated block DAG, simulated preview">
<div class="viz-head">
@ -330,7 +330,7 @@ footer .wrap{padding-block:48px 32px}
<div class="card reveal" style="display:flex;flex-direction:column;gap:16px">
<div class="verify-head"><div class="eyebrow">This hour's program</div><div class="mono" style="font-size:12px;color:var(--molten)">next in <span id="countdown">59:59</span></div></div>
<pre id="program" class="caret"></pre>
<p class="pt">A new program every hour. Last hour's chip is already obsolete.</p>
<p class="pt">A new program every hour. Last hour's chip is already obsolete. First live swap 4 Oct 2026: Apple, NVIDIA and AMD kept hashing through it, 0 rejected blocks.</p>
</div>
<div class="card reveal" style="display:flex;flex-direction:column;gap:16px">
<div class="eyebrow">Proofs sold to other chains</div>

View file

@ -300,7 +300,7 @@ body.all .pager{display:none}
<div class="tbl"><table>
<thead><tr><th>Property</th><th>RandomX, Monero</th><th>Igneum</th></tr></thead>
<tbody>
<tr><td>Hardware it is built for</td><td>CPUs. GPUs run it badly on purpose</td><td>GPUs. Any card, any vendor. Bit-exact on Apple and NVIDIA, measured</td></tr>
<tr><td>Hardware it is built for</td><td>CPUs. GPUs run it badly on purpose</td><td>GPUs. Any card, any vendor. Bit-exact on Apple, NVIDIA and AMD, measured</td></tr>
<tr><td>Random program</td><td>Per hash, interpreted in a virtual machine</td><td>Per hour, compiled to native GPU code, with a per-hash random data path</td></tr>
<tr><td>Dataset</td><td>About 2 GB, the same size since 2019, approximate</td><td>2 GB at genesis, growing every year past any chip's memory</td></tr>
<tr><td>Light verification</td><td>256 MB cache on a CPU, milliseconds</td><td>256 MB cache on a CPU, one warp under 10 ms, the measured gate</td></tr>
@ -310,7 +310,7 @@ body.all .pager{display:none}
<tr><td>Track record</td><td>No chip in seven years</td><td>Zero years. Every number above is measured and logged with the commands that produced it, and the specification, reference hash, test vectors and simulators are public now (github.com/igneum-network/spec); the node opens with the public benchmark in January 2027</td></tr>
</tbody>
</table></div>
<p>Measured so far: the same hourly program, generated on an Apple M5 Max, compiled by Apple's Metal and NVIDIA's CUDA on an RTX 5090, produced identical hashes on both, 192 of 192 across two programs. On a 1 GB dataset the 5090 ran at about 228 million hashes a second and the Mac at about 45 million, both bound by random memory access rather than arithmetic. Those are prototype figures: the prototype's dataset is still a simple formula a miner could compute instead of loading, which the next build replaces with a 256 MB cache construction, so the rates will change and are not mining rates. Inside the 5090's 96 MB cache the same program ran nearly six times faster, which is why the dataset starts at 2 GB and grows. AMD is the next test.</p>
<p>Measured so far: the same hourly program, generated on an Apple M5 Max, compiled by Apple's Metal and NVIDIA's CUDA on an RTX 5090, produced identical hashes on both, 192 of 192 across two programs. On a 1 GB dataset the 5090 ran at about 228 million hashes a second and the Mac at about 45 million, both bound by random memory access rather than arithmetic. Those are prototype figures: the prototype's dataset is still a simple formula a miner could compute instead of loading, which the next build replaces with a 256 MB cache construction, so the rates will change and are not mining rates. Inside the 5090's 96 MB cache the same program ran nearly six times faster, which is why the dataset starts at 2 GB and grows. On 4 October 2026 the live devnet crossed an hourly program change on all three vendors with no pause and no rejected block: a Mac at 26.7 million hashes a second, an RTX 5090 at 123 million and an integrated AMD chip at 2.7 million, every hash doing 128 distinct reads of the memory-hard dataset.</p>
</section>
<section id="proving">
@ -319,7 +319,7 @@ body.all .pager{display:none}
<h3>How a block gets proven</h3>
<p>Blocks carry transactions only and make no claim about state. Every node executes the ordered transactions natively at once, so users see their transaction land in about a second. The execution is then split into shards of a fixed proving cost. Shards are assigned by lot to eight provers for ten seconds, then open to anyone; there is no bond. Provers run them on consumer cards, and the shard proofs are folded by recursive aggregation into one proof for the block. That proof lands on-chain within about a minute at launch. Because the proof computes the state from the ordered sequence, a block with a wrong state cannot exist. Invalid transactions are skipped by rule, the way Kaspa skips conflicting spends.</p>
<h3>The proving budget</h3>
<p>Gas prices execution. Proving cost is a different number, so Igneum meters it separately: every transaction pays in both dimensions, and each block has a proving-cost budget set in consensus from measured prover throughput. A transaction that is cheap to run and expensive to prove pays for what it costs the provers. Shard size will be set so a 12 GB card proves one shard in about 20 seconds. That number is the first gate on the roadmap and has not been measured yet; once it has, the budget rises by schedule as hardware improves. The proof system is hash-based, which is what runs on consumer cards, and sits behind a versioned interface, so Igneum can adopt a better proof system when one exists by a miner-signalled release, and runs for ever on the current one if none is adopted.</p>
<p>Gas prices execution. Proving cost is a different number, so Igneum meters it separately: every transaction pays in both dimensions, and each block has a proving-cost budget set in consensus from measured prover throughput. A transaction that is cheap to run and expensive to prove pays for what it costs the provers. Shard size will be set so a 12 GB card proves one shard in about 20 seconds. That number is the first gate on the roadmap and is not measured yet. The first proofs exist: on 4 October 2026 an RTX 5090 proved a small two-transaction block in 1.4 seconds (2.7 seconds compressed), verified in 0.22 and 0.038 seconds, and a laptop CPU proved a three-shard block end to end in 19 minutes; a shard at the provisional size is the next run, and nothing is proven on the chain itself yet. Once the gate is measured, the budget rises by schedule as hardware improves. The proof system is hash-based, which is what runs on consumer cards, and sits behind a versioned interface, so Igneum can adopt a better proof system when one exists by a miner-signalled release, and runs for ever on the current one if none is adopted.</p>
<h3>Proving for everyone else</h3>
<p>The same miners accept proving jobs from other chains. Rollups post a job, a miner wins it, proves it, and is paid. The job market is permissionless. Jobs taken through Igneum's own market are paid in IGN and 10% of each fee is burned. The Igneum miner client also bids on other proving networks, where jobs are paid in those networks' currencies, and takes the best price. The proving market is small today. Igneum does not depend on it. No other proof-of-work chain has a seat in it.</p>
</section>
@ -329,7 +329,7 @@ body.all .pager{display:none}
<h3>Speed</h3>
<p>Igneum orders blocks with GHOSTDAG, the BlockDAG consensus proven on Kaspa. Blocks arrive in parallel and are ordered rather than orphaned, so the chain runs at one block a second at launch with scheduled steps to four and ten. A transaction is included in about one second, against twelve on Ethereum, and locked in about two minutes against roughly thirteen. Emission per block is a schedule keyed to difficulty-adjusted time, and every block in the window is paid at that rate, red or blue, so coins track blocks within the accuracy of the difficulty controller.</p>
<h3>Finality</h3>
<p>Every 30 seconds of chain a checkpoint forms, deep enough past the tip that the DAG will not reorder it. Every miner with at least 100 blocks in the last 30 days signs it, and the checkpoint locks when signatures representing two thirds of all the mining weight of those 30 days arrive. Once a checkpoint is locked it overrides the heaviest chain, so no amount of fresh hashrate can reorganise past it. In the chain's first month the weights behind those locks are thin, because nobody has a long history yet, and the chain leans on proof of work and its 12-hour finality depth the way every new proof-of-work chain does.</p>
<p>Every 30 seconds of chain a checkpoint forms, deep enough past the tip that the DAG will not reorder it. Every miner with at least 100 blocks in the last 30 days signs it, and the checkpoint locks when signatures representing two thirds of all the mining weight of those 30 days arrive. Once a checkpoint is locked it overrides the heaviest chain, so no amount of fresh hashrate can reorganise past it. In the chain's first month the weights behind those locks are thin, because nobody has a long history yet, and the chain leans on proof of work and its 12-hour finality depth the way every new proof-of-work chain does. On the devnet the first lock came two hours after genesis, at 77.4% of all weight from 17 vote keys (4 October 2026).</p>
<div class="pull">The word sustained is the whole defence. Block rewards go to whoever mines, new or old. The right to lock history is earned.</div>
<p>A miner's vote weight is simply the blocks it has mined over the trailing 30 days, measured by work, so splitting into many keys buys nothing and joining a pool costs nothing. Hashrate that arrived today holds almost none of it. Even an attacker who brought the whole network's hashrate would need ten days of mining in public to hold a third of the weight, and twenty days to hold two thirds. At 51% of the network they never reach two thirds at all while the honest miners keep mining. Rental is priced by the hour. The only route left is to drive honest miners off the chain and hold two thirds for a month on the public hashrate charts, which is the same limit Bitcoin lives with, with a month's warning attached.</p>
<p>Two further rules close the gaps. A lock needs two thirds of all 30-day weight, so finality pauses whenever less than two thirds of that weight is connected and signing, until it returns or ages out of the window, up to 30 days, and the chain runs on proof of work meanwhile. The node reports the pause. A key that stops signing is reported as absent within two hours, which is how operators see a pause coming. Beneath the latest lock the depth to rely on is the finality depth: a node never switches to a chain forked more than 12 hours of median time back, and a certified checkpoint shortens that to its own age. Kaspa's one-hour merge depth is a limit on which old blocks a new block may merge, not a reorganisation bound. Signing two different checkpoints at the same height is equivocation, provable by anyone, and it strips the key of its vote for 30 days.</p>
@ -508,7 +508,7 @@ body.all .pager{display:none}
<li><strong>A proof in seconds.</strong> Not at launch. Proving a full block today needs a cluster of 100 to 200 consumer GPUs, approximate, so Igneum launches with proofs within about a minute and tightens as hardware improves. Users still see their transaction land in one second.</li>
<li><strong>A chip is impossible.</strong> No. A chip is pointless, because the target moves before it ships. The honest efficiency ceiling for a fixed chip on a memory-bound program is under 2x, approximate, and Igneum's generator changes under it every hour.</li>
<li><strong>A guaranteed income floor.</strong> No. External proving is a small market today. Igneum's miners have the lowest cost in it, which is an edge and nothing more.</li>
<li><strong>Finality that no amount of hardware can break.</strong> No. A miner holding a third of the last 30 days of blocks can split finality during a network partition, and two thirds can lock a bad checkpoint for a double-spend bounded by the 12-hour finality depth. Reaching a third takes at least ten days of the whole network's hashrate, in public. That is harder than attacking Bitcoin, where a majority can reorganise at once, and it is the limit of proof of work without stake or an outside chain. Igneum chose those limits on purpose.</li>
<li><strong>Finality that no amount of hardware can break.</strong> No. A miner holding a third of the last 30 days of blocks can split finality during a network partition, and two thirds can lock a bad checkpoint for a double-spend bounded by the 12-hour finality depth. Reaching a third takes at least ten days of the whole network's hashrate, in public. That is harder than attacking Bitcoin, where a majority can reorganise at once, and it is the limit of proof of work without stake or an outside chain. Igneum chose those limits on purpose. The floor is also bounded in time: an honest partition that lasts long enough for each side's own new blocks to reach two thirds of its window locks on both sides, about ten days of a 30-day window at an even split, and an operator must then resolve it (measured on a test network, 4 October 2026).</li>
<li><strong>Finality that never pauses.</strong> No. A lock needs two thirds of all 30-day mining weight. Whenever less than two thirds of that weight is connected and signing, finality pauses until it returns or ages out of the window, up to 30 days. The chain keeps running on proof of work and the node reports the pause.</li>
<li><strong>A finished protocol.</strong> The sustained-mining finality rule is the newest piece and the one that external review will try hardest to break. The specification, the review and the benchmarks are published as they happen.</li>
</ul>

View file

@ -10,7 +10,7 @@
param(
[string]$Root = '',
[string[]]$Folders = @('proto-cuda/windows-app', 'proto-cuda/windows-miner', 'proto-cuda/windows-node',
'proving/windows-wsl2', 'relay/clients', 'packaging/windows', 'app/windows', 'tools/ci/windows'),
'proving/windows-wsl2', 'relay/clients', 'relay/playbooks', 'packaging/windows', 'app/windows', 'tools/ci/windows'),
[switch]$AnyVersion
)
$ErrorActionPreference = 'Stop'

110
tools/jobs.mjs Executable file
View file

@ -0,0 +1,110 @@
#!/usr/bin/env node
// Mac side of the remote jobs (app/igneum-app/src/jobs.rs, published by packaging/ota/publish-jobs.sh).
// node tools/jobs.mjs the published jobs file: fetched from the downloads host, signature checked
// node tools/jobs.mjs status per machine, from the log intake: the latest job run and its SUMMARY line
// node tools/jobs.mjs <job id> the result: the newest upload per label under run_id job-<id>-<machine>
// node tools/jobs.mjs <job id> --all every upload, oldest first (the 5-minute progress reports of a long job)
// node tools/jobs.mjs watch <job id> poll the intake every 30 s until every reporting machine is final
// Reads ~/.config/igneum/env (DATABASE_URL, the same Neon HTTP SQL as tools/logs.mjs), dl-token and
// ota-signing-key.pub. No dependencies.
import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { createPublicKey, verify } from 'node:crypto';
process.stdout.on('error', e => { if (e.code === 'EPIPE') process.exit(0); throw e; });
const cfg = n => { try { return readFileSync(`${homedir()}/.config/igneum/${n}`, 'utf8').trim(); } catch { return ''; } };
// the same intake reader as tools/logs.mjs (Neon HTTP SQL over fetch)
function db() {
const m = /^DATABASE_URL=(.*)$/m.exec(cfg('env'));
if (!m) { console.error('DATABASE_URL not found in ~/.config/igneum/env'); process.exit(1); }
const url = m[1].trim().replace(/^['"]|['"]$/g, '');
const host = new URL(url).hostname.replace('-pooler', '');
return async (query, params = []) => {
const r = await fetch(`https://${host}/sql`, { method: 'POST', headers: { 'Neon-Connection-String': url, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, params }) });
const j = await r.json();
if (!r.ok) throw new Error(j.message || JSON.stringify(j));
return j.rows;
};
}
const when = ts => new Date(ts).toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
const summaryOf = lines => { const l = (lines || '').split('\n')[0]; if (!l.startsWith('SUMMARY ')) return null; try { return JSON.parse(l.slice(8)); } catch { return null; } };
const [a, b] = process.argv.slice(2);
if (!a) {
const tok = cfg('dl-token');
if (!tok) { console.error('no ~/.config/igneum/dl-token'); process.exit(1); }
const base = `https://dl.igneum.network/dl/${tok}`;
const r = await fetch(`${base}/igneum-jobs.json`);
if (r.status === 404) { console.log('no jobs file published'); process.exit(0); }
if (!r.ok) { console.error(`jobs file: http ${r.status}`); process.exit(1); }
const bytes = Buffer.from(await r.arrayBuffer());
const sig = (await (await fetch(`${base}/igneum-jobs.json.sig`)).text()).trim();
const pub = cfg('ota-signing-key.pub');
let verified = 'not checked (no ~/.config/igneum/ota-signing-key.pub)';
if (pub) {
const key = createPublicKey({ key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), Buffer.from(pub, 'hex')]), format: 'der', type: 'spki' });
verified = verify(null, bytes, key, Buffer.from(sig, 'hex')) ? 'signature OK' : 'SIGNATURE DOES NOT VERIFY (the apps refuse this file)';
}
const f = JSON.parse(bytes.toString('utf8'));
console.log(`${base}/igneum-jobs.json: published ${f.published_at}, ${f.jobs.length} job(s), ${verified}`);
const now = Date.now();
for (const j of f.jobs) {
const exp = Date.parse(j.expires_at);
const t = j.target || {};
console.log(` ${exp < now ? 'EXPIRED ' : ''}${j.id} ${j.kind} to ${Array.isArray(t.machine_ids) ? t.machine_ids.join(',') : t.machine_ids} on ${t.platform || 'any'}${t.requires && t.requires.length ? ' needs ' + t.requires.join(',') : ''} until ${j.expires_at} ${j.title || ''}`);
const p = JSON.stringify(j.params || {});
console.log(` ${p.length > 180 ? p.slice(0, 180) + '...' : p}`);
}
process.exit(0);
}
const sql = db();
if (a === 'status') {
const rows = await sql(`
SELECT DISTINCT ON (machine) machine, run_id, label, received_at, lines FROM miner_logs
WHERE run_id LIKE 'job-%' AND label LIKE 'job-%' ORDER BY machine, received_at DESC`);
if (!rows.length) { console.log('no job reports in the intake yet'); process.exit(0); }
for (const r of rows) {
const s = summaryOf(r.lines);
console.log(`${r.machine.padEnd(28)} ${r.run_id.padEnd(44)} ${when(r.received_at)} ${s ? `${s.status} exit ${s.exit} after ${s.duration_s} s: ${s.summary}` : '(no SUMMARY line)'}`);
}
process.exit(0);
}
async function show(id, all) {
const rows = all
? await sql('SELECT id, run_id, received_at, label, machine, bytes, lines FROM miner_logs WHERE run_id LIKE $1 ORDER BY received_at ASC', [`job-${id}-%`])
: await sql('SELECT DISTINCT ON (run_id, label) id, run_id, received_at, label, machine, bytes, lines FROM miner_logs WHERE run_id LIKE $1 ORDER BY run_id, label, received_at DESC', [`job-${id}-%`]);
if (!rows.length) return null;
const final = [];
for (const r of rows) {
console.log(`===== id ${r.id} | ${r.run_id} | ${r.label} | ${r.machine} | ${when(r.received_at)} | ${r.bytes} bytes =====`);
const s = summaryOf(r.lines);
if (s) {
console.log(`SUMMARY: ${s.status} exit ${s.exit}, started ${s.started_at}${s.finished_at ? ', finished ' + s.finished_at : ''}, ${s.duration_s} s: ${s.summary}`);
if (s.results && s.results.length) for (const l of s.results) console.log(` ${l}`);
final.push(['done', 'failed', 'timeout', 'aborted'].includes(s.status));
if (!all) { const rest = r.lines.split('\n').slice(1); const tail = rest.slice(-30); console.log(tail.join('\n')); if (rest.length > 30) console.log(` (... ${rest.length - 30} more lines; --all prints every upload)`); }
else process.stdout.write(r.lines.endsWith('\n') ? r.lines : r.lines + '\n');
} else {
process.stdout.write(r.lines.endsWith('\n') ? r.lines : r.lines + '\n');
}
}
return final;
}
if (a === 'watch') {
if (!b) { console.error('watch <job id>'); process.exit(1); }
for (;;) {
const f = await show(b, false);
if (f && f.length && f.every(Boolean)) { console.log('final'); process.exit(0); }
console.log(`${f ? 'still running' : 'nothing yet'}; again in 30 s (Ctrl+C to stop)`);
await new Promise(r => setTimeout(r, 30000));
}
}
const f = await show(a, b === '--all');
if (!f) { console.error(`no uploads for job ${a} (run_id job-${a}-<machine>); the app reports when the job starts and every 5 minutes`); process.exit(1); }