From fa1dbc7e9d0807c3ad4d5a2391b563b31b73ccb2 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:52:45 +0000 Subject: [PATCH] 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 --- .github/workflows/windows.yml | 3 +- app/igneum-app/src/bin/ota-sign.rs | 30 +++- app/igneum-app/src/config.rs | 6 +- app/igneum-app/src/engine.rs | 94 ++++++++++- app/igneum-app/src/jobrun.rs | 8 +- app/igneum-app/src/main.rs | 2 + app/igneum-app/src/server.rs | 9 + app/igneum-app/src/state.rs | 41 +++++ app/igneum-app/ui/app.css | 9 + app/igneum-app/ui/app.js | 51 +++++- app/igneum-app/ui/index.html | 13 ++ docs/evidence.md | 78 +++++---- docs/plans/shard-test-pc2.md | 73 +++++++++ packaging/ota/README.md | 54 ++++++ packaging/ota/publish-jobs.sh | 253 +++++++++++++++++++++++++++++ relay/README.md | 22 ++- relay/playbooks/shard-test.ps1 | 164 +++++++++++++++++++ site/evidence.html | 60 +++---- site/index.html | 8 +- site/litepaper.html | 10 +- tools/ci/windows/check-ps51.ps1 | 2 +- tools/jobs.mjs | 110 +++++++++++++ 22 files changed, 1018 insertions(+), 82 deletions(-) create mode 100644 docs/plans/shard-test-pc2.md create mode 100755 packaging/ota/publish-jobs.sh create mode 100644 relay/playbooks/shard-test.ps1 create mode 100755 tools/jobs.mjs diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 8dae1c77a..ef8b57bd4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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 diff --git a/app/igneum-app/src/bin/ota-sign.rs b/app/igneum-app/src/bin/ota-sign.rs index a8afc8aac..87f71c6a2 100644 --- a/app/igneum-app/src/bin/ota-sign.rs +++ b/app/igneum-app/src/bin/ota-sign.rs @@ -7,9 +7,13 @@ //! igneum-ota-sign embedded prints the public key compiled into the app and its fingerprint //! igneum-ota-sign fingerprint //! igneum-ota-sign sha256 the file's sha256 and size, for the manifest +//! igneum-ota-sign sign-jobs the remote-jobs file (src/jobs.rs), same key +//! igneum-ota-sign verify-jobs #[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::>().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 | sign | verify | embedded | fingerprint | sha256 "); + eprintln!("usage: igneum-ota-sign keygen | sign | verify | embedded | fingerprint | sha256 | sign-jobs | verify-jobs "); std::process::exit(2); } } diff --git a/app/igneum-app/src/config.rs b/app/igneum-app/src/config.rs index 3aa285e6a..f8e33fcc2 100644 --- a/app/igneum-app/src/config.rs +++ b/app/igneum-app/src/config.rs @@ -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 } } } diff --git a/app/igneum-app/src/engine.rs b/app/igneum-app/src/engine.rs index 344f378eb..6514b7e64 100644 --- a/app/igneum-app/src/engine.rs +++ b/app/igneum-app/src/engine.rs @@ -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), /// 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, telemetry: Option, 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(); diff --git a/app/igneum-app/src/jobrun.rs b/app/igneum-app/src/jobrun.rs index 255ef3c0a..796ff5191 100644 --- a/app/igneum-app/src/jobrun.rs +++ b/app/igneum-app/src/jobrun.rs @@ -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) { + pub fn check_now(&mut self, shared: &Arc) -> Option { 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. diff --git a/app/igneum-app/src/main.rs b/app/igneum-app/src/main.rs index e240d5b9c..e06de70a9 100644 --- a/app/igneum-app/src/main.rs +++ b/app/igneum-app/src/main.rs @@ -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; diff --git a/app/igneum-app/src/server.rs b/app/igneum-app/src/server.rs index fa6682ecc..db0ff327d 100644 --- a/app/igneum-app/src/server.rs +++ b/app/igneum-app/src/server.rs @@ -263,3 +263,12 @@ fn api_post(shared: &Arc, path: &str, body: Value) -> Result 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 })) + } diff --git a/app/igneum-app/src/state.rs b/app/igneum-app/src/state.rs index 5cf4a0c1e..69b206fae 100644 --- a/app/igneum-app/src/state.rs +++ b/app/igneum-app/src/state.rs @@ -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, // RESULT and STAGE lines so far + pub last: JobHistory, + pub last_results: Vec, + pub history: Vec, } #[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, pub uptime_s: u64, pub now: f64, diff --git a/app/igneum-app/ui/app.css b/app/igneum-app/ui/app.css index f3268c2cd..5a2ab8610 100644 --- a/app/igneum-app/ui/app.css +++ b/app/igneum-app/ui/app.css @@ -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} diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js index 5a57b13dd..92cee27b8 100644 --- a/app/igneum-app/ui/app.js +++ b/app/igneum-app/ui/app.js @@ -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 ? '' + h.map(function (r) { + return ''; + }).join('') + '
jobkindstartedexitreport
' + esc(r.id) + '' + esc(r.kind) + '' + (r.started_at ? clock(r.started_at) : '') + '' + esc(r.status) + (r.status !== 'running' ? ' (' + r.exit + ')' : '') + '' + (r.uploaded ? 'uploaded' : (r.status === 'running' ? 'pending' : 'not uploaded')) + '
' : '

No job has run on this machine yet.

'; + } 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; } diff --git a/app/igneum-app/ui/index.html b/app/igneum-app/ui/index.html index 50755ae9f..ed9555ddd 100644 --- a/app/igneum-app/ui/index.html +++ b/app/igneum-app/ui/index.html @@ -35,6 +35,11 @@ +
@@ -266,6 +271,14 @@

+
+
remote jobs
+
+

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.

+

+

+
+
folders

diff --git a/docs/evidence.md b/docs/evidence.md index 9cdb3ee6f..0ad8d10e5 100644 --- a/docs/evidence.md +++ b/docs/evidence.md @@ -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 diff --git a/docs/plans/shard-test-pc2.md b/docs/plans/shard-test-pc2.md new file mode 100644 index 000000000..c90afa6be --- /dev/null +++ b/docs/plans/shard-test-pc2.md @@ -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--` 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//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 every 30 s until final; the result files land as labels result- + 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 | diff --git a/packaging/ota/README.md b/packaging/ota/README.md index fc87db389..ce9b926fe 100644 --- a/packaging/ota/README.md +++ b/packaging/ota/README.md @@ -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/jobs//` 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--` with the label `job-`; 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-`, `result-`, `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 | sign + node tools/jobs.mjs | status | [--all] | watch + +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`). diff --git a/packaging/ota/publish-jobs.sh b/packaging/ota/publish-jobs.sh new file mode 100755 index 000000000..248ed9fc9 --- /dev/null +++ b/packaging/ota/publish-jobs.sh @@ -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//), 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-- (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 [--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 [--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 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 " >&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//)" >&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() { # -> "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 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 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:-}" +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 diff --git a/relay/README.md b/relay/README.md index 7a36be132..187035de8 100644 --- a/relay/README.md +++ b/relay/README.md @@ -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--` | +| 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//c/`. 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-` or `mac-`) never reaches the intake, because `app/igneum-app/src/main.rs` names the file with its own `stamp_now()` while `engine.rs` uploads `app-.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 diff --git a/relay/playbooks/shard-test.ps1 b/relay/playbooks/shard-test.ps1 new file mode 100644 index 000000000..2efb18c93 --- /dev/null +++ b/relay/playbooks/shard-test.ps1 @@ -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 diff --git a/site/evidence.html b/site/evidence.html index 575d542d8..39d24819f 100644 --- a/site/evidence.html +++ b/site/evidence.html @@ -71,48 +71,50 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c
IGNEUM

Evidence

-

Every claim the homepage and the litepaper make, one row each, with one of five labels: designed (a decision, no code), implemented (code with passing test vectors), tested by the team (measured by the project on a named machine, in the engineering log), reproduced externally (a third party ran the published command and got the published result) and reviewed independently (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.

+

Every claim the homepage and the litepaper make, one row each, with one of five labels: designed (a decision, no code), implemented (code with passing test vectors), tested by the team (measured by the project on a named machine, in the engineering log), reproduced externally (a third party ran the published command and got the published result) and reviewed independently (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.

-
designed
7

A decision in the design document or the specification. No code, or a stub

+
designed
6

A decision in the design document or the specification. No code, or a stub

implemented
3

Code in the repository with test vectors that pass. Not measured as the claim

-
tested by the team
18

Measured or exercised by the project on a named machine, with the command in the log

+
tested by the team
21

Measured or exercised by the project on a named machine, with the command in the log

reproduced externally
0

None yet. The repository is private until January 2027

reviewed independently
0

None yet. What review would cost and who pays is in the funding plan

-
+
- - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + - + - + - - - + + + + +
#ClaimStatusVersion or commitReproducible testResult, date, machineIndependent verification
1A 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 teamigneum-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 yetnone yet
2The 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")
implementedrepo 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
3The 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 teamrepo 58a5a63; igneum-pow 0.1.0 (memhard.rs); proto-metal/MEMHARD.mdproto-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
4The 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 teamrepo f2e903e, 0f1fdaf; pack proto-cuda/packs/igneum-genesis-mh; igneum-pow 0.1.0The 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
5A 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 teamrepo 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
6The 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 teamrepo 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 Maxnone yet
7The devnet runs at one block a second
Homepage stats ("1 / s"); litepaper Speed; roadmap phase 3
tested by the teamrepo 9812466, e9328c6; fork worktree vendor/igneum-node-diff branch difficulty3-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
8Blocks are mined by GPUs on Apple and NVIDIA
Homepage live strip; journey phase 3 ("GPU miners on three vendors")
tested by the teamrepo 9812466, e9328c6; fork as row 1Metal 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 PCnone yet
9Blocks are mined by a GPU on AMD
Journey phase 3 ("three vendors")
implementedrepo 0f1fdaf; bound kernel kernel_bound.cl in the packproto-opencl/host.c --serve on an AMD device against a devnet nodeThe 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 verifiednone yet
10Checkpoints 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 teamrepo bbb264a (simulation), 60b1412 (fork run); fork "Finality: BLS12-381 vote keys ..." through "Miner: BLS identity ..."; spec 3.10sim/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 reproducednone yet
11Hashrate 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 teamrepo bbb264a; sim/finality_v2.pyScenario B of sim/finality_v2.py, seeds 7 and 11share(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 stubsnone yet
12The 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 teamrepo e9328c6; fork worktree vendor/igneum-node-diff branch difficulty; sim/difficulty/sim.pysim/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 stubsnone yet
13Every 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 teamrepo f5f8c80; fork worktree vendor/igneum-node-exec branch execution-layer; revm 43.0.3node 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 nodesnone yet
14Ethereum bytecode runs unchanged, with the documented differences of spec 7.1
Homepage Build card; litepaper Building
tested by the teamas row 13tools/evm-smoke/smoke.mjs: deploy via viem, increment, hashLoop, eth_estimateGas, eth_getLogsDeployment, 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 implementednone yet
15Every 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
designedspec 7.2; docs/design/execution-layer.md section 5None exists. The phase 2 benchmark standard is docs/benchmarks/proving-e2e.mdNo 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 targetnone yet
16A 12 GB card proves one shard in about 20 s
Litepaper Proving ("The proving budget"); roadmap gate 2
designedspec 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 backlogUnmeasured. A per-shard time can be met by shrinking the shard, so the project no longer uses it as a pass marknone yet
1A 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 teamigneum-pow 0.2.0; repo b27da39, 1292110, 100c5d7; fork devnet-v4 6457ca95The 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 measurednone yet
2The 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")
implementedrepo 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
3The 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 teamrepo 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.mdproto-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 unchangednone yet
4The 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 teamrepo f2e903e, 0f1fdaf (version 1 packs), b27da39 (version 2 packs igneum-genesis-mh, igneum-devnet-v4-epoch0); igneum-pow 0.2.0The 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
5A 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 teamrepo 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
6The 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 teamrepo 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 2026none yet
7The devnet runs at one block a second
Homepage stats ("1 / s"); litepaper Speed; roadmap phase 3
tested by the teamrepo 9812466, e9328c6, 8dae48b; fork devnet-v4 dc749905The 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
8Blocks are mined by GPUs on Apple and NVIDIA
Homepage live strip; journey phase 3 ("GPU miners on three vendors")
tested by the teamrepo 9812466, e9328c6, d7e1f89, 2309c8d; fork devnet-v4Metal 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 minednone yet
9Blocks are mined by a GPU on AMD
Journey phase 3 ("three vendors")
tested by the teamrepo 2c4b30f (generic OpenCL worker, --pack), 112acf6 (fault guards); bound kernel kernel_bound.cl in the packigneum-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 anythingnone yet
10Checkpoints 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 teamrepo 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.9The 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 lengthnone yet
11Hashrate 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 teamrepo bbb264a; sim/finality_v2.pyScenario B of sim/finality_v2.py, seeds 7 and 11share(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 yetnone yet
12The 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 teamrepo 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 --liveThe 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 opennone yet
13Every 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 teamrepo f5f8c80, 8dae48b; fork devnet-v4 dc749905; revm 43.0.3node 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 nodesnone yet
14Ethereum bytecode runs unchanged, with the documented differences of spec 7.1
Homepage Build card; litepaper Building
tested by the teamas 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 nodenone yet
15Every 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
implementedrepo 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.6proving/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
16A 12 GB card proves one shard in about 20 s
Litepaper Proving ("The proving budget"); roadmap gate 2
designedspec 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 marknone yet
17The 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"
designedspec 0.2 (Target); O-1.17Public 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
18The 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 teamrepo aba248d, f2a1a64, 4b95c5eRTX 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 runnone yet
19The 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 teamrepo c52307e, 58a5a63; proto-metal/TESTS.mdproto-metal/igneum-bench --fuzz --edge --stats --determinism --memcheck; bench-log "hardening tests" and the re-run on the memory-hard dataset10,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 onlynone yet
18The 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 teamrepo aba248d, f2a1a64, 4b95c5eRTX 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 2none yet
19The 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 teamrepo c52307e, 58a5a63, b27da39; proto-metal/TESTS.mdproto-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 onlynone yet
20No 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
implementedrepo 6ac80a3; fork "igneum-node devnet v0"; consensus/core/src/igneum.rs, coinbase.rscargo 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 happenednone yet
21The proving pool's 20% reaches shard provers and aggregators
Litepaper Economics; homepage "20% provers"
designedspec 5.3None. The pool output exists (row 20); the payout from it against proof records is unwrittenThe 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
21The proving pool's 20% reaches shard provers and aggregators
Litepaper Economics; homepage "20% provers"
designedspec 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 unwrittenThe 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 hardwarenone yet
22The 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 teamrepo f5f8c80; fork worktree vendor/igneum-node-exectools/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 throughoutnone yet
23No fee to any team, foundation or fund; 0 admin keys in consensus
Homepage Economics tiles and caption; litepaper "No fund, no foundation" and Governance
designedspec 5.5, 5.6 (decided 3 October 2026); spec 08Reading: 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
23No fee to any team, foundation or fund; 0 admin keys in consensus
Homepage Economics tiles and caption; litepaper "No fund, no foundation" and Governance
designedspec 5.5, 5.6 (decided 3 October 2026); spec 08Reading: 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
24External 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
designedspec 5.4None. 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 existsnone yet
25The 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 teamrepo 6ac80a3; fork consensus/core/src/igneum.rscargo 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 owednone yet
26A 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
designedspec 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 cardNone 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.4The 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
27The node survives malformed input, floods, withholding, partitions and eclipses
Litepaper Speed ("GHOSTDAG, the BlockDAG consensus proven on Kaspa"); spec 2
tested by the teamrepo 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 mergenone yet
28Headers 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 teamrepo 0953ec7; fork worktree vendor/igneum-node-r3 branch r3-fixes at 5166ee26measure_m15_attack_before_and_after (ignored test, release, --features igneum-pow); kaspa-pow 8, header_processor 1, p2p pow_guard 2 tests50 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 branchnone yet
26A 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
designedspec 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.mjsNone 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.4Since 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
27The node survives malformed input, floods, withholding, partitions and eclipses
Litepaper Speed ("GHOSTDAG, the BlockDAG consensus proven on Kaspa"); spec 2
tested by the teamrepo 394030c, 8dae48b, 6b5bd92; fork worktree vendor/igneum-node-harness and devnet-v4; tools/harness/; infra/cloud-devnet/experiments/partition.shtools/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 10none yet
28Headers 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 teamrepo 0953ec7, 8dae48b; fork worktree vendor/igneum-node-r3 branch r3-fixes at 5166ee26, merged into devnet-v4measure_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 node50 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 RPCnone yet
29Blocks 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 teamrepo 6b5bd92; infra/cloud-devnet/experiments/latency.sh, analyze.py; the Linux cross-build infra/cross/build-linux.sh12 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.md644 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 challengenone yet
30One 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 teamrepo 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 appnone yet
-

Click a column heading to sort; click again to reverse. Versions: igneum-pow 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 engineering log. The source of this page is docs/evidence.md in the repository.

+

Click a column heading to sort; click again to reverse. Versions: igneum-pow 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 engineering log. The source of this page is docs/evidence.md in the repository.

What would move a row

@@ -121,7 +123,7 @@ footer{border-top:1px solid var(--line);padding-block:32px 48px;font-size:13px;c
FromToWhat it takes
designedimplementedCode in this repository with test vectors that pass
reproduced externallyreviewed independentlyA named reviewer's published finding on that version. Funding for review is docs/plans/funding.md
anythe row's status falls backA new version of the code or rule the row names
-
© 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.
+
© 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.