Merge origin/master into ca3-coord: the status file keeps the 7 October rows (a20d238f's merge had taken its 6 October side), the bench-log keeps both sides
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
95
.github/workflows/ci.yml
vendored
|
|
@ -1,10 +1,19 @@
|
|||
# CI on every push and pull request (private repository, free runner minutes).
|
||||
#
|
||||
# What runs: the lottery-hash crate's tests (igneum-pow, release profile), the census tool's build, the two Python
|
||||
# simulators' --quick modes (each under two minutes), the site build with an internal link check, the gh-free
|
||||
# identity grep of the public export list (tools/ci/forbidden-strings.txt), and the no-secrets check of the tree
|
||||
# (tools/ci/no-secrets-check.sh: no file named like a key of ~/.config/igneum, no 64-hex value assigned to a
|
||||
# token/key/secret name outside tests and the allowlist; docs/security/keys.md).
|
||||
# simulators' --quick modes (each under two minutes), and the tree gate: every fast check in ONE script,
|
||||
# tools/ci/pre-push.sh (site build and link check, the ledger sentences, the identity grep of the public export list
|
||||
# and the served site, Windows-valid paths, workflow shell syntax, the copied-sources and playbook classes, the unit
|
||||
# tests, the no-secrets check). The pre-push hook runs the SAME script before a push to master or release-*, so the
|
||||
# local gate and CI cannot drift (6 October 2026: 131 red `ci` runs in three days, 92 of them on master, every one a
|
||||
# tree check that would have failed on the pushing machine in under 25 s; docs/analysis/ci-failures-2026-10-06.md).
|
||||
#
|
||||
# Where it runs: `pow` and `sims` go to the box's runner (igneum-build-1, rustc pinned, sccache read-only, 48 jobs)
|
||||
# when the repository variable IGNEUM_CI_RUNNER is `box`, else to ubuntu-latest (docs/plans/ci-self-hosted.md; GitHub
|
||||
# has no fallback in runs-on, the variable is the switch). The `site` job stays on GitHub's machines. The `red` job
|
||||
# runs on the box after any failed master or release-* run and records the failure for the watcher
|
||||
# (tools/ci/red-watch.mjs; infra/build-server/ci-red): one line per run to the hidden updates channel and to
|
||||
# /srv/ci-red/red.jsonl, so nobody opens the Actions page to learn master is red.
|
||||
#
|
||||
# What does not run, on purpose: the node fork (vendor/igneum-node*, a rusty-kaspa fork of about 500 crates with
|
||||
# rocksdb, blst and the execution layer) is gitignored here and too big for the free runners today (a cold build is
|
||||
|
|
@ -17,7 +26,7 @@ on:
|
|||
jobs:
|
||||
pow:
|
||||
name: igneum-pow tests, igneum-census build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ vars.IGNEUM_CI_RUNNER == 'box' && fromJSON('["self-hosted", "linux", "x64", "igneum-build-1"]') || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: toolchain
|
||||
|
|
@ -32,13 +41,15 @@ jobs:
|
|||
run: cargo build --release
|
||||
sims:
|
||||
name: simulators, quick modes
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ vars.IGNEUM_CI_RUNNER == 'box' && fromJSON('["self-hosted", "linux", "x64", "igneum-build-1"]') || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
if: vars.IGNEUM_CI_RUNNER != 'box' # the box has python3 and numpy from provision.sh
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- run: python3 -m pip install --quiet numpy
|
||||
if: vars.IGNEUM_CI_RUNNER != 'box'
|
||||
- name: finality_v2.py --quick (under two minutes)
|
||||
working-directory: sim
|
||||
run: time timeout 120 python3 finality_v2.py --quick > finality_quick.md
|
||||
|
|
@ -59,52 +70,32 @@ jobs:
|
|||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
- name: site build
|
||||
run: node site/build.mjs
|
||||
- name: internal link check of site/*.html
|
||||
run: node tools/ci/link-check.mjs
|
||||
- name: identity grep of the public export list
|
||||
run: bash tools/ci/identity-check.sh
|
||||
- name: no conflict markers in tracked files
|
||||
run: bash tools/ci/no-conflict-markers.sh
|
||||
- name: PowerShell drive-reference check (a `$name:` inside a double-quoted string is a 5.1 parse error)
|
||||
run: bash tools/ci/ps-drive-ref-check.sh
|
||||
- name: copied sources are re-stamped before a build
|
||||
run: bash tools/ci/copied-sources-check.sh
|
||||
- name: override params files parse with no duplicate key (the duplicate-field class, 6 October 2026)
|
||||
run: bash tools/ci/override-json-check.sh
|
||||
- name: second-engine playbooks log to a file and end their tree (C35)
|
||||
run: bash tools/ci/second-engine-check.sh
|
||||
- name: no playbook quits, pauses or resumes the installed app (self-test first, then the tree)
|
||||
run: bash tools/ci/playbook-quit-check.sh --self-test && bash tools/ci/playbook-quit-check.sh
|
||||
- name: the signer is never piped into head
|
||||
run: bash tools/ci/signer-pipe-check.sh
|
||||
- name: bash bodies in PowerShell job scripts pass bash -n, the lost-quote class (self-test first, then the tree)
|
||||
run: bash tools/ci/bash-body-check.sh --self-test && bash tools/ci/bash-body-check.sh
|
||||
- name: run jobs test their fetched kit before use, the wiped-jobs-folder class (self-test first, then the tree)
|
||||
run: bash tools/ci/kit-path-check.sh --self-test && bash tools/ci/kit-path-check.sh
|
||||
- name: every Windows spawn of the app runs with a hidden console (self-test first, then the tree)
|
||||
run: node tools/ci/windows-spawn-check.mjs --self-test && node tools/ci/windows-spawn-check.mjs
|
||||
- name: pinned guest programs match their manifest and are built only by pin-guests.sh
|
||||
run: bash tools/ci/pinned-guests-check.sh
|
||||
- name: root prover playbooks kill the GPU server and unlink its socket (the root-socket class, 5 October 2026)
|
||||
run: bash tools/ci/prover-socket-check.sh
|
||||
- name: commit-string gate self-test (the empty-commit class of 6 October 2026; the gate itself runs in build-remote.sh, cross-remote.sh and cross-build.sh on every node binary)
|
||||
run: bash tools/ci/commit-string-check.sh --self-test
|
||||
- name: build server remote checkout self-test (the stale-overlay class of 6 October 2026)
|
||||
run: bash infra/build-server/remote-run.sh --self-test
|
||||
- name: no secret file names and no 64-hex secrets in the tree (self-test first, then the tree)
|
||||
run: bash tools/ci/no-secrets-check.sh --self-test && bash tools/ci/no-secrets-check.sh
|
||||
- name: faucet unit tests (validation, the daily limits, the signed transaction; keccak, RLP and secp256k1 vectors)
|
||||
run: node --test site/api/faucet.test.mjs
|
||||
- name: explorer and public stats unit tests (search router, formatters, emission rule against the node's own test values, the documented API fields from a fixture)
|
||||
run: node --test site/lib/explorer.test.mjs site/lib/emission.test.mjs site/api/public-stats.test.mjs
|
||||
- name: the tree gate, tools/ci/pre-push.sh --ci (the same script the pre-push hook runs; one line per check, a red check prints its output)
|
||||
run: bash tools/ci/pre-push.sh --ci
|
||||
- name: public stats API answers with the documented fields (the live site; master only, the endpoints exist there after the merge)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: node tools/ci/public-api-check.mjs https://igneum.network
|
||||
- name: ship tool self-test (version bump, the dl-both and public manifest helpers)
|
||||
run: node tools/ship-app.mjs --self-test
|
||||
- name: relay unit tests (parsers, secret compare, the wake endpoint)
|
||||
run: node --test relay/test/parse.test.mjs relay/test/auth.test.mjs relay/test/wake.test.mjs relay/test/ember.test.mjs
|
||||
- name: miner app notice strip and update card (ordering, keys, wording, timers, when the card shows)
|
||||
run: node --test app/igneum-app/ui/notices.test.mjs app/igneum-app/ui/update-card.test.mjs app/igneum-app/ui/view.test.mjs app/igneum-app/ui/tune-line.test.mjs
|
||||
|
||||
red:
|
||||
# Runs only when a master or release-* run has a failed job, on the box's own runner (not a GitHub-hosted machine:
|
||||
# the billing block of 6 October 2026, 18:37Z to 20:10Z, failed every hosted job at start and nobody was told).
|
||||
# tools/ci/red-watch.mjs record appends ONE line for this run to /srv/ci-red/red.jsonl (idempotent per run attempt);
|
||||
# the box's igneum-ci-red.timer posts each new line once to the hidden updates channel. Never blocks a release:
|
||||
# it reads the run, writes one line, and ends.
|
||||
name: red watcher (master and release-* only; one line per failed run to the updates channel and the box file)
|
||||
needs: [pow, sims, site]
|
||||
if: ${{ failure() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-')) }}
|
||||
runs-on: [self-hosted, linux, x64, igneum-build-1]
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
actions: read # the run's jobs API (the first real red run, 21:19Z: the default token answered 403 and the line carried no step)
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: tools/ci
|
||||
- name: record this run (one line, the failed jobs and their first failed step, from the run's own API)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
RED_WATCH_TITLE: ${{ github.event.head_commit.message }}
|
||||
run: node tools/ci/red-watch.mjs record --file /srv/ci-red/red.jsonl
|
||||
|
|
|
|||
21
.github/workflows/windows.yml
vendored
|
|
@ -293,3 +293,24 @@ jobs:
|
|||
path: build/inputs-artifact/
|
||||
retention-days: 90
|
||||
if-no-files-found: error
|
||||
|
||||
red:
|
||||
# The red watcher for the Windows pipeline (see ci.yml `red`): one line per failed master or release-* run to the
|
||||
# hidden updates channel and /srv/ci-red/red.jsonl on the box, recorded by the box's own runner.
|
||||
name: red watcher (master and release-* only; one line per failed run to the updates channel and the box file)
|
||||
needs: [parse, build]
|
||||
if: ${{ failure() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-')) }}
|
||||
runs-on: [self-hosted, linux, x64, igneum-build-1]
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
actions: read # the run's jobs API (the first real red run, 21:19Z: the default token answered 403 and the line carried no step)
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: tools/ci
|
||||
- name: record this run (one line, the failed jobs and their first failed step, from the run's own API)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
RED_WATCH_TITLE: ${{ github.event.head_commit.message }}
|
||||
run: node tools/ci/red-watch.mjs record --file /srv/ci-red/red.jsonl
|
||||
|
|
|
|||
5
.gitignore
vendored
|
|
@ -31,3 +31,8 @@ vendor/igneum-node-ship/
|
|||
# Trademark instruction packs name the director and the applicant company; never in the repository
|
||||
brand/trademark/pbip-pack/
|
||||
brand/trademark/*.zip
|
||||
|
||||
# the Discord bot and reddit bot dry-run renders
|
||||
tools/community/out/
|
||||
# the GPU workers built on igneum-build-1 (tools/workers-remote.sh); binaries, never committed
|
||||
infra/cross/out-workers-box/
|
||||
|
|
|
|||
2
app/igneum-app/Cargo.lock
generated
|
|
@ -219,7 +219,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "igneum-app"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
dependencies = [
|
||||
"ed25519-dalek",
|
||||
"getrandom",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "igneum-app"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
edition = "2021"
|
||||
description = "Igneum Miner engine: supervises the node, the miner and the GPU workers, and serves the dashboard on 127.0.0.1"
|
||||
license = "MIT"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
1 ICON "igneum.ico"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,3,13,0
|
||||
PRODUCTVERSION 0,3,13,0
|
||||
FILEVERSION 0,3,14,0
|
||||
PRODUCTVERSION 0,3,14,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
|
|
@ -20,12 +20,12 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Igneum"
|
||||
VALUE "FileDescription", "Igneum Miner engine"
|
||||
VALUE "FileVersion", "0.3.13"
|
||||
VALUE "FileVersion", "0.3.14"
|
||||
VALUE "InternalName", "igneum-app"
|
||||
VALUE "LegalCopyright", "Igneum contributors"
|
||||
VALUE "OriginalFilename", "igneum-app.exe"
|
||||
VALUE "ProductName", "Igneum Miner"
|
||||
VALUE "ProductVersion", "0.3.13"
|
||||
VALUE "ProductVersion", "0.3.14"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ pub struct CardPref {
|
|||
pub sweep_class: String,
|
||||
#[serde(default)]
|
||||
pub sweep_source: String,
|
||||
/// Ember 2: the memory clock the last tune chose (0 = the driver's default)
|
||||
#[serde(default)]
|
||||
pub sweep_mem_mhz: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
|
|
@ -90,6 +93,15 @@ pub struct Settings {
|
|||
/// unanswered prompt switches it back off with a notice, no retries.
|
||||
#[serde(default)]
|
||||
pub power_control: bool,
|
||||
/// Ember 2 (6 October 2026): the tune's goal ("efficiency" = most MH per watt within 10% of the top rate,
|
||||
/// "balanced" = within 1%, "rate" = the fastest point), the electricity price in pence per kWh for the £/day
|
||||
/// reading on each card, and the hill-climb switch (memory up, core down from the fleet prior; off = the ladders).
|
||||
#[serde(default = "balanced")]
|
||||
pub tune_goal: String,
|
||||
#[serde(default)]
|
||||
pub power_price_pence: f64,
|
||||
#[serde(default)]
|
||||
pub tune_climb: bool,
|
||||
/// When this install first ran (unix s), for the "first hour after install" sweep.
|
||||
#[serde(default)]
|
||||
pub installed_at: u64,
|
||||
|
|
@ -113,13 +125,16 @@ pub struct Settings {
|
|||
fn one() -> u32 {
|
||||
1
|
||||
}
|
||||
fn balanced() -> String {
|
||||
"balanced".into()
|
||||
}
|
||||
fn yes() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
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, remote_jobs: true, prove: false, sweep: true, power_control: false, installed_at: 0, dev_fee: true, fee_total: 0, proof_verify_trust: false, prove_default_applied: false }
|
||||
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, prove: false, sweep: true, power_control: false, tune_goal: "balanced".into(), power_price_pence: 0.0, tune_climb: false, installed_at: 0, dev_fee: true, fee_total: 0, proof_verify_trust: false, prove_default_applied: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ use std::time::{Duration, Instant};
|
|||
/// The power steps, percent of the card's default limit, highest first (the same ladder as src/sweep.rs).
|
||||
pub const POWER_STEPS_PCT: [u32; 6] = [100, 90, 80, 70, 60, 50];
|
||||
/// The clock steps, percent of the card's maximum core clock, highest first; 100 = unlocked (the card's own boost).
|
||||
pub const CLOCK_STEPS_PCT: [u32; 5] = [100, 90, 80, 70, 60];
|
||||
/// 6 October 2026, run 6: the 5090's best MH/W sat on the 60% floor (1,854 MHz: 0.563 MH/W, the rate within 0.15%),
|
||||
/// so the ladder and the floor go to 45% of the maximum; the 1% rate tolerance is the guard below that
|
||||
pub const CLOCK_STEPS_PCT: [u32; 7] = [100, 90, 80, 70, 60, 50, 45];
|
||||
/// A card's clock floor when the vendor reports none: this share of its maximum core clock.
|
||||
pub const CLOCK_FLOOR_PCT: u32 = 60;
|
||||
pub const CLOCK_FLOOR_PCT: u32 = 45;
|
||||
/// A point may lose this much rate against the fastest point and still win on MH per watt (the manifest can change it).
|
||||
pub const RATE_TOLERANCE_PCT: f64 = 1.0;
|
||||
/// A step whose hottest GPU reading reaches this is marked hot and cannot win (the engine aborts at 90).
|
||||
|
|
@ -49,6 +51,10 @@ pub struct Limits {
|
|||
pub clock_max_mhz: u32,
|
||||
/// the lowest cap the vendor allows (the ADLX gmax_range floor); 0 = CLOCK_FLOOR_PCT of the maximum
|
||||
pub clock_min_mhz: u32,
|
||||
/// Ember 2: the memory clock the card runs at by default and the vendor's maximum (nvidia-smi clocks.mem and
|
||||
/// clocks.max.mem); 0 = no memory knob (AMD through ADLX on RDNA 4 exposes none)
|
||||
pub mem_default_mhz: u32,
|
||||
pub mem_max_mhz: u32,
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
|
|
@ -66,6 +72,13 @@ impl Limits {
|
|||
}
|
||||
mhz.clamp(self.clock_floor(), self.clock_max_mhz)
|
||||
}
|
||||
/// A memory clock inside the vendor's range; 0 stays 0 (the default).
|
||||
pub fn clamp_mem(&self, mhz: u32) -> u32 {
|
||||
if mhz == 0 || self.mem_max_mhz == 0 || self.mem_default_mhz == 0 {
|
||||
return 0;
|
||||
}
|
||||
mhz.clamp(self.mem_default_mhz, self.mem_max_mhz)
|
||||
}
|
||||
/// The watts a power percent asks for, inside the card's min and max, rounded to a watt.
|
||||
pub fn watts_for(&self, pct: u32) -> f64 {
|
||||
let mut w = self.power_default_w * pct as f64 / 100.0;
|
||||
|
|
@ -79,13 +92,15 @@ impl Limits {
|
|||
}
|
||||
}
|
||||
|
||||
/// One setting of the two knobs.
|
||||
/// One setting of the knobs (Ember 2 adds the memory clock: 0 = the driver's default).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Point {
|
||||
/// the core clock cap in MHz; 0 = unlocked
|
||||
pub clock_mhz: u32,
|
||||
/// the power limit, percent of the default
|
||||
pub power_pct: u32,
|
||||
/// the memory clock in MHz (NVIDIA `-lmc`, locked to one value); 0 = the driver's default
|
||||
pub mem_mhz: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
|
@ -96,6 +111,8 @@ pub enum Kind {
|
|||
Clock,
|
||||
/// the fleet prior and one neighbour
|
||||
Confirm,
|
||||
/// Ember 2: a hill-climb probe
|
||||
Climb,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
@ -106,11 +123,53 @@ pub struct Step {
|
|||
pub kind: Kind,
|
||||
}
|
||||
|
||||
/// What the tune is for (Settings > Ember Tune > goal). The rate floor is the share of the best rate seen a point
|
||||
/// must keep to win on MH per watt: efficiency keeps 90%, balanced 99% (the 1% rule of lever 3), maximum rate
|
||||
/// takes the fastest point and uses MH per watt only to break ties.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Goal {
|
||||
Efficiency,
|
||||
Balanced,
|
||||
MaxRate,
|
||||
}
|
||||
|
||||
impl Goal {
|
||||
pub fn parse(s: &str) -> Goal {
|
||||
match s {
|
||||
"efficiency" | "eff" => Goal::Efficiency,
|
||||
"rate" | "max_rate" | "maximum" => Goal::MaxRate,
|
||||
_ => Goal::Balanced,
|
||||
}
|
||||
}
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Goal::Efficiency => "efficiency",
|
||||
Goal::Balanced => "balanced",
|
||||
Goal::MaxRate => "rate",
|
||||
}
|
||||
}
|
||||
/// The rate tolerance the choice rule uses, percent under the best rate.
|
||||
pub fn tolerance_pct(&self, manifest_default: f64) -> f64 {
|
||||
match self {
|
||||
Goal::Efficiency => 10.0,
|
||||
Goal::Balanced => manifest_default,
|
||||
Goal::MaxRate => 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pounds a day for a draw at a price in pence per kWh: watts × 24 h / 1000 × price / 100.
|
||||
pub fn pounds_per_day(watts: f64, pence_per_kwh: f64) -> f64 {
|
||||
watts * 24.0 / 1000.0 * pence_per_kwh / 100.0
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PlanKind {
|
||||
Full,
|
||||
Confirm,
|
||||
Baseline,
|
||||
/// Ember 2: the hill-climb over memory up and core down from the start point
|
||||
Climb,
|
||||
}
|
||||
|
||||
impl PlanKind {
|
||||
|
|
@ -119,6 +178,7 @@ impl PlanKind {
|
|||
PlanKind::Full => "full",
|
||||
PlanKind::Confirm => "confirm",
|
||||
PlanKind::Baseline => "baseline",
|
||||
PlanKind::Climb => "climb",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -134,9 +194,82 @@ pub struct Plan {
|
|||
power: Vec<Step>,
|
||||
clock_pcts: Vec<u32>,
|
||||
fixed: Vec<Step>,
|
||||
/// Ember 2 (Climb): the start point, the step sizes and the step budget
|
||||
climb: Option<Climb>,
|
||||
}
|
||||
|
||||
/// The hill-climb's shape: from `start`, each probe moves the memory clock up by `mem_step` or the core clock down
|
||||
/// by `core_step` (or both), keeps the move when the goal's score improves, else turns to the other knob; a
|
||||
/// refused step (a fault on it) backs that knob off for good. At most `budget` steps including the start.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Climb {
|
||||
pub start: Point,
|
||||
pub mem_step: u32,
|
||||
pub core_step: u32,
|
||||
pub budget: usize,
|
||||
pub goal: Goal,
|
||||
}
|
||||
|
||||
impl Plan {
|
||||
/// Ember 2: the hill-climb. The start is the fleet prior (or the card's current point); a probe step is 5% of
|
||||
/// the memory range above the default (0 when the card has no memory knob) and 5% of the maximum core clock;
|
||||
/// five steps of 60 s converge in under 10 minutes.
|
||||
pub fn climb(limits: &Limits, start: Point, goal: Goal, tolerance_pct: f64) -> Plan {
|
||||
let mem_step = if limits.mem_max_mhz > limits.mem_default_mhz { ((limits.mem_max_mhz - limits.mem_default_mhz) / 20).max(25) } else { 0 };
|
||||
let core_step = if limits.clock_max_mhz > 0 { (limits.clock_max_mhz / 20).max(25) } else { 0 };
|
||||
let start = Point { clock_mhz: limits.clamp_clock(start.clock_mhz), power_pct: start.power_pct.clamp(50, 100), mem_mhz: limits.clamp_mem(start.mem_mhz) };
|
||||
Plan { kind: PlanKind::Climb, limits: limits.clone(), before: start, tolerance_pct: goal.tolerance_pct(tolerance_pct), power: Vec::new(), clock_pcts: Vec::new(), fixed: Vec::new(), climb: Some(Climb { start, mem_step, core_step, budget: 5, goal }) }
|
||||
}
|
||||
|
||||
/// The goal's score of a row: MH per watt for efficiency and balanced, the rate for maximum rate.
|
||||
pub fn score(&self, r: &Row) -> f64 {
|
||||
match self.climb.as_ref().map(|c| c.goal) {
|
||||
Some(Goal::MaxRate) => r.mhs,
|
||||
_ => r.eff,
|
||||
}
|
||||
}
|
||||
|
||||
/// The climb's next probe from the rows so far: None when the budget is spent or no move is left.
|
||||
fn climb_next(&self, rows: &[Row]) -> Option<Step> {
|
||||
let c = self.climb.as_ref()?;
|
||||
let step = |p: Point| Step { point: p, watts: self.limits.watts_for(p.power_pct), kind: Kind::Climb };
|
||||
if rows.is_empty() {
|
||||
return Some(step(c.start));
|
||||
}
|
||||
if rows.len() >= c.budget {
|
||||
return None;
|
||||
}
|
||||
// the best usable row so far is the hill's top; a knob that produced a marked (refused) row is backed off
|
||||
let best = rows.iter().filter(|r| r.usable()).max_by(|a, b| self.score(a).partial_cmp(&self.score(b)).unwrap_or(std::cmp::Ordering::Equal))?;
|
||||
let refused_mem = rows.iter().any(|r| !r.usable() && r.point.mem_mhz > best.point.mem_mhz);
|
||||
let refused_core = rows.iter().any(|r| !r.usable() && r.point.clock_mhz != 0 && (best.point.clock_mhz == 0 || r.point.clock_mhz < best.point.clock_mhz));
|
||||
let last = rows.last()?;
|
||||
let mem_up = |p: Point| -> Option<Point> {
|
||||
if c.mem_step == 0 || refused_mem { return None; }
|
||||
let base = if p.mem_mhz == 0 { self.limits.mem_default_mhz } else { p.mem_mhz };
|
||||
let m = self.limits.clamp_mem(base + c.mem_step);
|
||||
(m > 0 && m != p.mem_mhz && m != base).then_some(Point { mem_mhz: m, ..p })
|
||||
};
|
||||
let core_down = |p: Point| -> Option<Point> {
|
||||
if c.core_step == 0 || refused_core { return None; }
|
||||
let base = if p.clock_mhz == 0 { self.limits.clock_max_mhz } else { p.clock_mhz };
|
||||
let k = self.limits.clamp_clock(base.saturating_sub(c.core_step));
|
||||
(k > 0 && k != p.clock_mhz).then_some(Point { clock_mhz: k, ..p })
|
||||
};
|
||||
let tried = |p: Point| rows.iter().any(|r| r.point == p);
|
||||
// the last move improved: keep going the same way from the top; else turn: memory first, then core, then both
|
||||
let last_improved = last.usable() && last.point == best.point && rows.len() > 1;
|
||||
let last_was_mem = rows.len() > 1 && last.point.mem_mhz != rows[rows.len() - 2].point.mem_mhz;
|
||||
let candidates: Vec<Option<Point>> = if last_improved && last_was_mem {
|
||||
vec![mem_up(best.point), core_down(best.point)]
|
||||
} else if last_improved {
|
||||
vec![core_down(best.point), mem_up(best.point)]
|
||||
} else {
|
||||
vec![mem_up(best.point), core_down(best.point), mem_up(best.point).and_then(core_down)]
|
||||
};
|
||||
candidates.into_iter().flatten().find(|p| !tried(*p)).map(step)
|
||||
}
|
||||
|
||||
/// Power 100% to 50% at the unlocked clock (duplicate watts dropped, as the card's floor clamps them), then the
|
||||
/// clock ladder 90% to the floor of the maximum core clock at the chosen power. A card without a readable
|
||||
/// maximum clock gets the power ladder only; a card without a default limit gets the clock ladder only.
|
||||
|
|
@ -148,41 +281,44 @@ impl Plan {
|
|||
if power.last().map(|s: &Step| (s.watts - w).abs() < 0.5).unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
power.push(Step { point: Point { clock_mhz: 0, power_pct: pct }, watts: w, kind: Kind::Power });
|
||||
power.push(Step { point: Point { clock_mhz: 0, power_pct: pct, mem_mhz: 0 }, watts: w, kind: Kind::Power });
|
||||
}
|
||||
}
|
||||
let clock_pcts = if limits.clock_max_mhz > 0 { CLOCK_STEPS_PCT[1..].to_vec() } else { Vec::new() };
|
||||
Plan { kind: PlanKind::Full, limits: limits.clone(), before, tolerance_pct, power, clock_pcts, fixed: Vec::new() }
|
||||
Plan { kind: PlanKind::Full, limits: limits.clone(), before, tolerance_pct, power, clock_pcts, fixed: Vec::new(), climb: None }
|
||||
}
|
||||
|
||||
/// The prior's point, then one neighbour: the next clock step up when the prior caps the clock (is the cap
|
||||
/// costing rate?), else one power step down (is there efficiency left?).
|
||||
pub fn confirm(limits: &Limits, prior: Point, before: Point, tolerance_pct: f64) -> Plan {
|
||||
let p = Point { clock_mhz: limits.clamp_clock(prior.clock_mhz), power_pct: prior.power_pct.clamp(50, 100) };
|
||||
let p = Point { clock_mhz: limits.clamp_clock(prior.clock_mhz), power_pct: prior.power_pct.clamp(50, 100), mem_mhz: limits.clamp_mem(prior.mem_mhz) };
|
||||
let first = Step { point: p, watts: limits.watts_for(p.power_pct), kind: Kind::Confirm };
|
||||
let neighbour = if p.clock_mhz > 0 && limits.clock_max_mhz > 0 {
|
||||
let up = p.clock_mhz + limits.clock_max_mhz / 10;
|
||||
let clock = if up >= limits.clock_max_mhz { 0 } else { limits.clamp_clock(up) };
|
||||
Point { clock_mhz: clock, power_pct: p.power_pct }
|
||||
Point { clock_mhz: clock, power_pct: p.power_pct, mem_mhz: p.mem_mhz }
|
||||
} else {
|
||||
Point { clock_mhz: p.clock_mhz, power_pct: (p.power_pct.saturating_sub(10)).max(50) }
|
||||
Point { clock_mhz: p.clock_mhz, power_pct: (p.power_pct.saturating_sub(10)).max(50), mem_mhz: p.mem_mhz }
|
||||
};
|
||||
let mut fixed = vec![first];
|
||||
if neighbour != p {
|
||||
fixed.push(Step { point: neighbour, watts: limits.watts_for(neighbour.power_pct), kind: Kind::Confirm });
|
||||
}
|
||||
Plan { kind: PlanKind::Confirm, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), fixed }
|
||||
Plan { kind: PlanKind::Confirm, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), fixed, climb: None }
|
||||
}
|
||||
|
||||
/// One step at the card's current point: the before number, and all a measure-only card (Apple, or NVIDIA
|
||||
/// with Power control off) reports.
|
||||
pub fn baseline(limits: &Limits, before: Point, tolerance_pct: f64) -> Plan {
|
||||
let fixed = vec![Step { point: before, watts: limits.watts_for(before.power_pct), kind: Kind::Baseline }];
|
||||
Plan { kind: PlanKind::Baseline, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), fixed }
|
||||
Plan { kind: PlanKind::Baseline, limits: limits.clone(), before, tolerance_pct, power: Vec::new(), clock_pcts: Vec::new(), fixed, climb: None }
|
||||
}
|
||||
|
||||
/// How many steps the plan has at most (the clock ladder counts whether or not it runs).
|
||||
pub fn len(&self) -> usize {
|
||||
if let Some(c) = &self.climb {
|
||||
return c.budget;
|
||||
}
|
||||
self.fixed.len() + self.power.len() + self.clock_pcts.len()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
|
|
@ -191,6 +327,9 @@ impl Plan {
|
|||
|
||||
/// The next step after `rows` (one row per step done so far), or None when the plan is complete.
|
||||
pub fn next(&self, rows: &[Row]) -> Option<Step> {
|
||||
if self.climb.is_some() {
|
||||
return self.climb_next(rows);
|
||||
}
|
||||
let i = rows.len();
|
||||
if !self.fixed.is_empty() {
|
||||
return self.fixed.get(i).cloned();
|
||||
|
|
@ -207,7 +346,7 @@ impl Plan {
|
|||
if rows.last().map(|r| r.point.clock_mhz == clock).unwrap_or(false) {
|
||||
return None;
|
||||
}
|
||||
Some(Step { point: Point { clock_mhz: clock, power_pct }, watts: self.limits.watts_for(power_pct), kind: Kind::Clock })
|
||||
Some(Step { point: Point { clock_mhz: clock, power_pct, mem_mhz: 0 }, watts: self.limits.watts_for(power_pct), kind: Kind::Clock })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -305,9 +444,10 @@ impl Row {
|
|||
/// `TUNE card=<label> step=<i> clock=<MHz> cap=<pct> limit=<W> watts=<W> mhs=<x> eff=<MH/W> gclk=<MHz> mclk=<MHz> tmax=<C> mark=<m>`
|
||||
pub fn line(&self, card: &str, i: usize) -> String {
|
||||
format!(
|
||||
"TUNE card={card} step={i} clock={} cap={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} gclk={:.0} mclk={:.0} tmax={:.0} mark={}{}",
|
||||
"TUNE card={card} step={i} clock={} cap={} mem={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} gclk={:.0} mclk={:.0} tmax={:.0} mark={}{}",
|
||||
self.point.clock_mhz,
|
||||
self.point.power_pct,
|
||||
self.point.mem_mhz,
|
||||
self.limit,
|
||||
self.watts,
|
||||
self.mhs,
|
||||
|
|
@ -320,7 +460,7 @@ impl Row {
|
|||
)
|
||||
}
|
||||
pub fn json(&self) -> serde_json::Value {
|
||||
serde_json::json!({ "clock_mhz": self.point.clock_mhz, "power_pct": self.point.power_pct, "limit_w": self.limit, "watts": r1(self.watts), "mhs": r2(self.mhs), "eff": r4(self.eff), "gclk": self.gclk.round(), "mclk": self.mclk.round(), "tmax": self.tmax.round(), "faults": self.faults, "mark": self.mark.unwrap_or(Mark::NoReadings).name() })
|
||||
serde_json::json!({ "clock_mhz": self.point.clock_mhz, "power_pct": self.point.power_pct, "mem_mhz": self.point.mem_mhz, "limit_w": self.limit, "watts": r1(self.watts), "mhs": r2(self.mhs), "eff": r4(self.eff), "gclk": self.gclk.round(), "mclk": self.mclk.round(), "tmax": self.tmax.round(), "faults": self.faults, "mark": self.mark.unwrap_or(Mark::NoReadings).name() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -454,7 +594,7 @@ pub fn prior_of(tuning: Option<&serde_json::Value>, key: &str, min_samples: u32)
|
|||
if samples < min_samples.max(1) {
|
||||
return None;
|
||||
}
|
||||
let point = Point { clock_mhz: n("clock_mhz") as u32, power_pct: (n("power_pct") as u32).clamp(50, 100) };
|
||||
let point = Point { clock_mhz: n("clock_mhz") as u32, power_pct: (n("power_pct") as u32).clamp(50, 100), mem_mhz: n("mem_mhz") as u32 };
|
||||
if point.power_pct == 0 && point.clock_mhz == 0 {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -463,7 +603,7 @@ pub fn prior_of(tuning: Option<&serde_json::Value>, key: &str, min_samples: u32)
|
|||
|
||||
/// The fleet record: one `TUNE {json}` line. `machine` is a hash of the install id (never the id, never an address).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn record_json(ts: f64, machine_hash: &str, app: &str, os: &str, card: &str, vendor: &str, driver: &str, class: &str, plan: PlanKind, rows: &[Row], chosen: Option<&Row>, before: Option<&Row>) -> serde_json::Value {
|
||||
pub fn record_json(ts: f64, machine_hash: &str, app: &str, os: &str, card: &str, vendor: &str, driver: &str, class: &str, plan: PlanKind, rows: &[Row], chosen: Option<&Row>, before: Option<&Row>, floor: bool) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"ts": ts.round(),
|
||||
"machine": machine_hash,
|
||||
|
|
@ -482,6 +622,8 @@ pub fn record_json(ts: f64, machine_hash: &str, app: &str, os: &str, card: &str,
|
|||
"eff": chosen.map(|r| r4(r.eff)).unwrap_or(0.0),
|
||||
"mhs": chosen.map(|r| r2(r.mhs)).unwrap_or(0.0),
|
||||
"watts": chosen.map(|r| r1(r.watts)).unwrap_or(0.0),
|
||||
"floor": floor,
|
||||
"note": if floor { "floor, not optimum" } else { "" },
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -591,9 +733,28 @@ impl Run {
|
|||
self.rows.first().map(|r| r.mclk).unwrap_or(0.0)
|
||||
}
|
||||
|
||||
/// Seconds left in the plan from here: the rest of this step plus (settle + hold + a 10 s apply) per step to come.
|
||||
pub fn eta_s(&self, now: Instant) -> i64 {
|
||||
let per = (self.timing.settle + self.timing.hold).as_secs() as i64 + 10;
|
||||
let this = match &self.phase {
|
||||
Phase::Applying { .. } => per,
|
||||
Phase::Settling { since } => per - 10 - now.duration_since(*since).as_secs().min(per as u64) as i64,
|
||||
Phase::Holding { since } => (self.timing.hold.as_secs() as i64 - now.duration_since(*since).as_secs() as i64).max(0),
|
||||
Phase::Finishing { .. } | Phase::Done => 0,
|
||||
};
|
||||
let left = self.plan.len().saturating_sub(self.rows.len() + 1) as i64;
|
||||
this.max(0) + left * per
|
||||
}
|
||||
|
||||
/// The phase in words for the card row.
|
||||
pub fn words(&self, now: Instant) -> String {
|
||||
let what = |p: &Point| if p.clock_mhz > 0 { format!("{} MHz · {}%", p.clock_mhz, p.power_pct) } else { format!("{}%", p.power_pct) };
|
||||
let what = |p: &Point| {
|
||||
let mut w = if p.clock_mhz > 0 { format!("{} MHz · {}%", p.clock_mhz, p.power_pct) } else { format!("{}%", p.power_pct) };
|
||||
if p.mem_mhz > 0 {
|
||||
w.push_str(&format!(" · mem {}", p.mem_mhz));
|
||||
}
|
||||
w
|
||||
};
|
||||
let cur = self.current.as_ref().map(|s| what(&s.point)).unwrap_or_default();
|
||||
let n = self.rows.len() + 1;
|
||||
let of = self.plan.len();
|
||||
|
|
@ -611,10 +772,15 @@ impl Run {
|
|||
fn applied(step: &Step, rb: Readback, before_w: f64) -> bool {
|
||||
let power_ok = rb.limit_w <= 0.0 || (rb.limit_w - step.watts).abs() < 1.5;
|
||||
let power_changed = (step.watts - before_w).abs() >= 1.5;
|
||||
if step.point.clock_mhz > 0 || !power_changed {
|
||||
if step.point.clock_mhz > 0 || step.point.mem_mhz > 0 || !power_changed {
|
||||
rb.acked && power_ok
|
||||
} else if rb.limit_w <= 0.0 {
|
||||
// a card with no limit readback at all (AMD through ADLX reports an offset, never watts; run 6 on
|
||||
// 6 October 2026 aborted the 9070 XT's ladder at its first step on "card reports 0 W"): the
|
||||
// acknowledgement is the proof
|
||||
rb.acked
|
||||
} else {
|
||||
rb.limit_w > 0.0 && power_ok
|
||||
power_ok
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -719,6 +885,13 @@ impl Run {
|
|||
Verdict::NoReadings => None,
|
||||
},
|
||||
PlanKind::Full => choose(&self.rows, self.plan.tolerance_pct),
|
||||
PlanKind::Climb => {
|
||||
if self.plan.climb.as_ref().map(|c| c.goal) == Some(Goal::MaxRate) {
|
||||
self.rows.iter().filter(|r| r.usable()).max_by(|a, b| a.mhs.partial_cmp(&b.mhs).unwrap_or(std::cmp::Ordering::Equal)).cloned()
|
||||
} else {
|
||||
choose(&self.rows, self.plan.tolerance_pct)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -733,6 +906,21 @@ pub fn tuned_line(mhs: f64, watts: f64, eff: f64) -> String {
|
|||
format!("Tuned: {mhs:.1} MH/s at {watts:.0} W ({eff:.3} MH/W)")
|
||||
}
|
||||
|
||||
/// The row's line for a baseline (measure-only) result: the card was measured as it runs, nothing was set.
|
||||
/// (6 October 2026: PC 1's rows read "Tuned: 114.2 MH/s at 305 W" for a baseline, which is not a tune.)
|
||||
pub fn measured_line(mhs: f64, watts: f64, eff: f64) -> String {
|
||||
format!("Measured: {mhs:.1} MH/s at {watts:.0} W ({eff:.3} MH/W)")
|
||||
}
|
||||
|
||||
/// The row's line for a plan's result.
|
||||
pub fn result_line(kind: PlanKind, mhs: f64, watts: f64, eff: f64) -> String {
|
||||
if kind == PlanKind::Baseline {
|
||||
measured_line(mhs, watts, eff)
|
||||
} else {
|
||||
tuned_line(mhs, watts, eff)
|
||||
}
|
||||
}
|
||||
|
||||
/// Why a card cannot be tuned beyond measuring, or None when both knobs are available.
|
||||
pub fn control_reason(vendor: &str, limits: &Limits, device: &str, power_control: bool, amd_helper: bool) -> Option<String> {
|
||||
match vendor {
|
||||
|
|
@ -754,7 +942,7 @@ mod tests {
|
|||
/// PC 1's RTX 5090 (nvidia-smi, 4 and 5 October 2026): default 575 W, min 400 W, max 600 W; clocks.max.gr is
|
||||
/// read at the first tune (3,090 MHz is the shape used here, not a measurement).
|
||||
fn l5090() -> Limits {
|
||||
Limits { power_default_w: 575.0, power_min_w: 400.0, power_max_w: 600.0, clock_max_mhz: 3090, clock_min_mhz: 0 }
|
||||
Limits { power_default_w: 575.0, power_min_w: 400.0, power_max_w: 600.0, clock_max_mhz: 3090, clock_min_mhz: 0, mem_default_mhz: 13801, mem_max_mhz: 14001 }
|
||||
}
|
||||
|
||||
fn row_at(p: Point, watts: f64, mhs: f64) -> Row {
|
||||
|
|
@ -763,10 +951,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn the_full_plan_is_the_power_ladder_then_the_clock_ladder_at_the_chosen_power() {
|
||||
let plan = Plan::full(&l5090(), Point { clock_mhz: 0, power_pct: 80 }, 1.0);
|
||||
assert_eq!(plan.len(), 5 + 4, "five power steps (60% and 50% clamp to 400 W; one kept) and four clock steps");
|
||||
let plan = Plan::full(&l5090(), Point { clock_mhz: 0, power_pct: 80, mem_mhz: 0 }, 1.0);
|
||||
assert_eq!(plan.len(), 5 + 6, "five power steps (60% and 50% clamp to 400 W; one kept) and six clock steps (90% down to 45%)");
|
||||
let first = plan.next(&[]).unwrap();
|
||||
assert_eq!((first.point, first.watts, first.kind), (Point { clock_mhz: 0, power_pct: 100 }, 575.0, Kind::Power));
|
||||
assert_eq!((first.point, first.watts, first.kind), (Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, 575.0, Kind::Power));
|
||||
// the power ladder: 575, 518, 460, 403, 400
|
||||
let mut rows = Vec::new();
|
||||
let mut watts_seen = Vec::new();
|
||||
|
|
@ -781,35 +969,59 @@ mod tests {
|
|||
// the clock ladder rides the chosen power point: a flat ladder ties on efficiency and rate, the lowest draw wins (100%)
|
||||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.kind, Kind::Clock);
|
||||
assert_eq!(s.point, Point { clock_mhz: 2781, power_pct: 100 });
|
||||
assert_eq!(s.point, Point { clock_mhz: 2781, power_pct: 100, mem_mhz: 0 });
|
||||
rows.push(row_at(s.point, 250.0, 123.8));
|
||||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.point.clock_mhz, 2472);
|
||||
rows.push(row_at(s.point, 220.0, 123.5));
|
||||
rows.push(row_at(plan.next(&rows).unwrap().point, 200.0, 118.0));
|
||||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.point.clock_mhz, 1854, "60% of 3,090 is the floor");
|
||||
assert_eq!(s.point.clock_mhz, 1854, "60% of 3,090");
|
||||
rows.push(row_at(s.point, 180.0, 100.0));
|
||||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.point.clock_mhz, 1545, "50%");
|
||||
rows.push(row_at(s.point, 170.0, 90.0));
|
||||
let s = plan.next(&rows).unwrap();
|
||||
assert_eq!(s.point.clock_mhz, 1390, "45% of 3,090 is the floor (6 October 2026)");
|
||||
rows.push(row_at(s.point, 160.0, 80.0));
|
||||
assert_eq!(plan.next(&rows), None);
|
||||
// the choice: 2,472 MHz keeps 99.6% of the top rate at 220 W = 0.561 MH/W; 2,163 MHz (118 MH/s) is outside the 1% tolerance
|
||||
let best = choose(&rows, 1.0).unwrap();
|
||||
assert_eq!(best.point, Point { clock_mhz: 2472, power_pct: 100 });
|
||||
assert_eq!(best.point, Point { clock_mhz: 2472, power_pct: 100, mem_mhz: 0 });
|
||||
// a wider tolerance lets the 2,163 MHz step (0.590 MH/W, 4.8% slower) win
|
||||
assert_eq!(choose(&rows, 5.0).unwrap().point.clock_mhz, 2163);
|
||||
// no power limits, clocks only; no clocks, power only; nothing, empty
|
||||
assert_eq!(Plan::full(&Limits { clock_max_mhz: 2000, ..Default::default() }, Point::default(), 1.0).len(), 4);
|
||||
assert_eq!(Plan::full(&Limits { clock_max_mhz: 2000, ..Default::default() }, Point::default(), 1.0).len(), 6);
|
||||
assert_eq!(Plan::full(&Limits { power_default_w: 300.0, ..Default::default() }, Point::default(), 1.0).len(), 6);
|
||||
assert!(Plan::full(&Limits::default(), Point::default(), 1.0).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_power_step_on_a_card_without_a_limit_readback_is_applied_on_the_acknowledgement() {
|
||||
// AMD through ADLX: the limit reads back as an offset, never watts (run 6, 6 October 2026)
|
||||
let step = Step { point: Point { clock_mhz: 0, power_pct: 90, mem_mhz: 0 }, watts: 90.0, kind: Kind::Power };
|
||||
assert!(Run::applied(&step, Readback { limit_w: 0.0, acked: true }, 100.0));
|
||||
assert!(!Run::applied(&step, Readback { limit_w: 0.0, acked: false }, 100.0));
|
||||
// NVIDIA: the watts must read back
|
||||
assert!(!Run::applied(&step, Readback { limit_w: 100.0, acked: true }, 100.0));
|
||||
assert!(Run::applied(&step, Readback { limit_w: 90.0, acked: false }, 100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_baseline_result_reads_measured_and_a_tune_reads_tuned() {
|
||||
assert_eq!(result_line(PlanKind::Baseline, 114.2, 305.0, 0.3744), "Measured: 114.2 MH/s at 305 W (0.374 MH/W)");
|
||||
assert_eq!(result_line(PlanKind::Full, 127.71, 226.8, 0.5632), "Tuned: 127.7 MH/s at 227 W (0.563 MH/W)");
|
||||
assert_eq!(result_line(PlanKind::Confirm, 127.71, 226.8, 0.5632), "Tuned: 127.7 MH/s at 227 W (0.563 MH/W)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limits_never_exceed_the_vendor_or_undercut_the_floor() {
|
||||
let l = l5090();
|
||||
assert_eq!(l.clock_floor(), 1854);
|
||||
assert_eq!(l.clamp_clock(1000), 1854);
|
||||
assert_eq!(l.clock_floor(), 1390);
|
||||
assert_eq!(l.clamp_clock(1000), 1390);
|
||||
assert_eq!(l.clamp_clock(5000), 3090);
|
||||
assert_eq!(l.clamp_clock(0), 0, "unlocked stays unlocked");
|
||||
assert_eq!(Limits { clock_max_mhz: 3000, clock_min_mhz: 2100, ..Default::default() }.clamp_clock(1500), 2100, "the vendor's floor wins over the 60% rule");
|
||||
assert_eq!(Limits { clock_max_mhz: 3000, clock_min_mhz: 2100, ..Default::default() }.clamp_clock(1500), 2100, "the vendor's floor wins over the 45% rule");
|
||||
assert_eq!(l.watts_for(100), 575.0);
|
||||
assert_eq!(l.watts_for(50), 400.0);
|
||||
assert_eq!(Limits { power_default_w: 300.0, power_max_w: 250.0, ..Default::default() }.watts_for(100), 250.0);
|
||||
|
|
@ -817,7 +1029,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn the_choice_keeps_the_best_mh_per_watt_within_the_rate_tolerance() {
|
||||
let p = |c: u32, pct: u32| Point { clock_mhz: c, power_pct: pct };
|
||||
let p = |c: u32, pct: u32| Point { clock_mhz: c, power_pct: pct, mem_mhz: 0 };
|
||||
// a card that gets more efficient as the cap drops until it collapses: 70% wins (0.280 MH/W), 60% is 35% slower
|
||||
let rows = vec![row_at(p(0, 100), 560.0, 124.0), row_at(p(0, 90), 510.0, 123.5), row_at(p(0, 80), 455.0, 123.2), row_at(p(0, 70), 400.0, 122.9), row_at(p(0, 60), 345.0, 80.0)];
|
||||
assert_eq!(choose(&rows, 1.0).unwrap().point, p(0, 70));
|
||||
|
|
@ -844,7 +1056,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn the_guards_mark_a_step_so_it_cannot_win() {
|
||||
let step = Step { point: Point { clock_mhz: 2000, power_pct: 100 }, watts: 575.0, kind: Kind::Clock };
|
||||
let step = Step { point: Point { clock_mhz: 2000, power_pct: 100, mem_mhz: 0 }, watts: 575.0, kind: Kind::Clock };
|
||||
let good = Samples { draws: vec![250.0, 251.0, 249.0], rates: vec![123.0], gclks: vec![1998.0], mclks: vec![2505.0], tmax: 70.0, faults: 0, unapplied: false };
|
||||
assert_eq!(Row::from_samples(&step, &good, 2505.0).mark, Some(Mark::Ok));
|
||||
// one rejected or mismatched hash: faulted
|
||||
|
|
@ -862,15 +1074,15 @@ mod tests {
|
|||
assert!(!r.usable());
|
||||
assert!(r.line("c", 3).contains("mark=no_readings"), "{}", r.line("c", 3));
|
||||
let r = Row::from_samples(&step, &good, 0.0);
|
||||
assert_eq!(r.line("nvidia-ae432dc7-1", 7), "TUNE card=nvidia-ae432dc7-1 step=7 clock=2000 cap=100 limit=575 watts=250.0 mhs=123.00 eff=0.4920 gclk=1998 mclk=2505 tmax=70 mark=ok");
|
||||
assert_eq!(r.line("nvidia-ae432dc7-1", 7), "TUNE card=nvidia-ae432dc7-1 step=7 clock=2000 cap=100 mem=0 limit=575 watts=250.0 mhs=123.00 eff=0.4920 gclk=1998 mclk=2505 tmax=70 mark=ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_fault_during_a_step_reverts_it_and_the_run_goes_on() {
|
||||
let t0 = Instant::now();
|
||||
let timing = Timing { settle: Duration::from_secs(2), hold: Duration::from_secs(4), apply: Duration::from_secs(30) };
|
||||
let limits = Limits { power_default_w: 300.0, power_min_w: 150.0, power_max_w: 300.0, clock_max_mhz: 0, clock_min_mhz: 0 };
|
||||
let plan = Plan::full(&limits, Point { clock_mhz: 0, power_pct: 80 }, 1.0);
|
||||
let limits = Limits { power_default_w: 300.0, power_min_w: 150.0, power_max_w: 300.0, clock_max_mhz: 0, clock_min_mhz: 0, mem_default_mhz: 0, mem_max_mhz: 0 };
|
||||
let plan = Plan::full(&limits, Point { clock_mhz: 0, power_pct: 80, mem_mhz: 0 }, 1.0);
|
||||
let mut run = Run::new(0, "0", "c", plan, 240.0, false, timing, t0);
|
||||
let mut t = t0;
|
||||
let mut limit = 240.0;
|
||||
|
|
@ -919,13 +1131,13 @@ mod tests {
|
|||
#[test]
|
||||
fn the_confirm_plan_checks_the_prior_and_its_neighbour() {
|
||||
let l = l5090();
|
||||
let prior = Point { clock_mhz: 2472, power_pct: 100 };
|
||||
let plan = Plan::confirm(&l, prior, Point { clock_mhz: 0, power_pct: 80 }, 1.0);
|
||||
let prior = Point { clock_mhz: 2472, power_pct: 100, mem_mhz: 0 };
|
||||
let plan = Plan::confirm(&l, prior, Point { clock_mhz: 0, power_pct: 80, mem_mhz: 0 }, 1.0);
|
||||
assert_eq!(plan.len(), 2);
|
||||
let a = plan.next(&[]).unwrap();
|
||||
assert_eq!((a.point, a.kind), (prior, Kind::Confirm));
|
||||
let b = plan.next(&[row_at(prior, 220.0, 123.5)]).unwrap();
|
||||
assert_eq!(b.point, Point { clock_mhz: 2781, power_pct: 100 }, "one clock step up");
|
||||
assert_eq!(b.point, Point { clock_mhz: 2781, power_pct: 100, mem_mhz: 0 }, "one clock step up");
|
||||
// the neighbour within 1%: the prior stands
|
||||
let rows = vec![row_at(prior, 220.0, 123.5), row_at(b.point, 250.0, 123.8)];
|
||||
assert!(matches!(confirm_verdict(&rows, 1.0), Verdict::Keep(r) if r.point == prior));
|
||||
|
|
@ -934,20 +1146,20 @@ mod tests {
|
|||
assert!(matches!(confirm_verdict(&rows, 1.0), Verdict::FullDue { .. }));
|
||||
assert_eq!(confirm_verdict(&[], 1.0), Verdict::NoReadings);
|
||||
// an unlocked prior at 100%: the neighbour is one power step down; at the top clock the neighbour is unlocked
|
||||
let plan = Plan::confirm(&l, Point { clock_mhz: 0, power_pct: 100 }, Point::default(), 1.0);
|
||||
assert_eq!(plan.next(&[row_at(Point { clock_mhz: 0, power_pct: 100 }, 1.0, 1.0)]).unwrap().point, Point { clock_mhz: 0, power_pct: 90 });
|
||||
let plan = Plan::confirm(&l, Point { clock_mhz: 2900, power_pct: 100 }, Point::default(), 1.0);
|
||||
assert_eq!(plan.next(&[row_at(Point { clock_mhz: 2900, power_pct: 100 }, 1.0, 1.0)]).unwrap().point.clock_mhz, 0);
|
||||
let plan = Plan::confirm(&l, Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, Point::default(), 1.0);
|
||||
assert_eq!(plan.next(&[row_at(Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, 1.0, 1.0)]).unwrap().point, Point { clock_mhz: 0, power_pct: 90, mem_mhz: 0 });
|
||||
let plan = Plan::confirm(&l, Point { clock_mhz: 2900, power_pct: 100, mem_mhz: 0 }, Point::default(), 1.0);
|
||||
assert_eq!(plan.next(&[row_at(Point { clock_mhz: 2900, power_pct: 100, mem_mhz: 0 }, 1.0, 1.0)]).unwrap().point.clock_mhz, 0);
|
||||
// a prior outside the vendor's range is clamped, never applied as is
|
||||
let plan = Plan::confirm(&l, Point { clock_mhz: 9000, power_pct: 30 }, Point::default(), 1.0);
|
||||
assert_eq!(plan.next(&[]).unwrap().point, Point { clock_mhz: 3090, power_pct: 50 });
|
||||
let plan = Plan::confirm(&l, Point { clock_mhz: 9000, power_pct: 30, mem_mhz: 0 }, Point::default(), 1.0);
|
||||
assert_eq!(plan.next(&[]).unwrap().point, Point { clock_mhz: 3090, power_pct: 50, mem_mhz: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_baseline_plan_measures_the_card_as_it_runs() {
|
||||
let t0 = Instant::now();
|
||||
let timing = Timing { settle: Duration::from_secs(1), hold: Duration::from_secs(2), apply: Duration::from_secs(3) };
|
||||
let before = Point { clock_mhz: 0, power_pct: 80 };
|
||||
let before = Point { clock_mhz: 0, power_pct: 80, mem_mhz: 0 };
|
||||
let mut run = Run::new(0, "0", "c", Plan::baseline(&l5090(), before, 1.0), 460.0, false, timing, t0);
|
||||
// nothing acknowledges the request (Power control is off): the apply window passes and the hold starts anyway
|
||||
let mut t = t0;
|
||||
|
|
@ -979,8 +1191,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn the_record_and_the_prior_round_trip_through_the_manifest_shape() {
|
||||
let chosen = row_at(Point { clock_mhz: 2472, power_pct: 100 }, 220.0, 123.5);
|
||||
let rec = record_json(1_791_230_000.0, "8f3a2c1d", "0.3.10", "windows", "NVIDIA GeForce RTX 5090", "nvidia", "581.57", "l128w16", PlanKind::Full, &[chosen.clone()], Some(&chosen), None);
|
||||
let chosen = row_at(Point { clock_mhz: 2472, power_pct: 100, mem_mhz: 0 }, 220.0, 123.5);
|
||||
let rec = record_json(1_791_230_000.0, "8f3a2c1d", "0.3.10", "windows", "NVIDIA GeForce RTX 5090", "nvidia", "581.57", "l128w16", PlanKind::Full, &[chosen.clone()], Some(&chosen), None, false);
|
||||
assert_eq!(rec["floor"], false);
|
||||
assert_eq!(rec["card"], "NVIDIA_GeForce_RTX_5090");
|
||||
assert_eq!(rec["key"], "NVIDIA_GeForce_RTX_5090|581|l128w16");
|
||||
assert_eq!(rec["driver_major"], "581");
|
||||
|
|
@ -999,7 +1212,7 @@ mod tests {
|
|||
let s = settings_of(Some(&tuning));
|
||||
assert_eq!(s, Settings { enabled: true, min_samples: 5, tolerance_pct: 1.0, period_s: PERIOD_S });
|
||||
let p = prior_of(Some(&tuning), "NVIDIA_GeForce_RTX_5090|581|l128w16", s.min_samples).unwrap();
|
||||
assert_eq!(p.point, Point { clock_mhz: 2472, power_pct: 100 });
|
||||
assert_eq!(p.point, Point { clock_mhz: 2472, power_pct: 100, mem_mhz: 0 });
|
||||
assert_eq!(p.samples, 7);
|
||||
assert!(prior_of(Some(&tuning), "AMD_Radeon_RX_9070_XT|32|l128w16", 5).is_none(), "3 samples are under the floor");
|
||||
assert!(prior_of(Some(&tuning), "AMD_Radeon_RX_9070_XT|32|l128w16", 3).is_some());
|
||||
|
|
@ -1016,6 +1229,84 @@ mod tests {
|
|||
assert_eq!(program_class(0, 0), "v2");
|
||||
}
|
||||
|
||||
/// Ember 2: a synthetic memory-latency-bound card. Rate rises 1% per 100 MHz of memory above the default and
|
||||
/// falls only 0.3% per 100 MHz of core below the maximum; draw falls 8 W per 100 MHz of core and rises 2 W per
|
||||
/// 100 MHz of memory. The climb must walk memory up and core down and converge in under five probes.
|
||||
fn synthetic(p: Point) -> Row {
|
||||
let mem = if p.mem_mhz == 0 { 13801.0 } else { p.mem_mhz as f64 };
|
||||
let core = if p.clock_mhz == 0 { 3090.0 } else { p.clock_mhz as f64 };
|
||||
let mhs = 127.0 * (1.0 + 0.015 * (mem - 13801.0) / 100.0) * (1.0 - 0.003 * (3090.0 - core) / 100.0);
|
||||
let watts = 310.0 - 8.0 * (3090.0 - core) / 100.0 + 2.0 * (mem - 13801.0) / 100.0;
|
||||
Row { point: p, limit: 575.0, watts, mhs, eff: mhs / watts, draws: 12, rates: 6, gclk: core, mclk: mem, tmax: 65.0, faults: 0, mark: Some(Mark::Ok) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_climb_walks_memory_up_and_core_down_and_converges_in_five_probes() {
|
||||
// a 2 GHz memory range above the default (the headroom a 5090 has in practice; PC 1's driver reports 14,001)
|
||||
let l = Limits { mem_max_mhz: 15801, ..l5090() };
|
||||
let plan = Plan::climb(&l, Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, Goal::Balanced, 1.0);
|
||||
assert_eq!(plan.len(), 5);
|
||||
let mut rows = Vec::new();
|
||||
while let Some(step) = plan.next(&rows) {
|
||||
assert_eq!(step.kind, Kind::Climb);
|
||||
rows.push(synthetic(step.point));
|
||||
}
|
||||
assert_eq!(rows.len(), 5, "the budget");
|
||||
assert_eq!(rows[0].point, Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, "it starts at the start point");
|
||||
// every probe moved one knob the right way: memory never down, core never up, and both inside the vendor's range
|
||||
for w in rows.windows(2) {
|
||||
let (a, b) = (w[0].point, w[1].point);
|
||||
assert!(b.mem_mhz >= a.mem_mhz || b.mem_mhz == 0, "{a:?} -> {b:?}");
|
||||
assert!(b.mem_mhz <= 15801 && (b.clock_mhz == 0 || b.clock_mhz >= 1854), "{b:?}");
|
||||
}
|
||||
let best = choose(&rows, plan.tolerance_pct).unwrap();
|
||||
assert!(best.eff > rows[0].eff, "the chosen point beats the start: {:.4} > {:.4}", best.eff, rows[0].eff);
|
||||
assert!(best.point.mem_mhz > 13801 || (best.point.clock_mhz > 0 && best.point.clock_mhz < 3090), "it moved a knob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refused_probe_backs_that_knob_off_for_good() {
|
||||
let l = Limits { mem_max_mhz: 15801, ..l5090() };
|
||||
let plan = Plan::climb(&l, Point { clock_mhz: 0, power_pct: 100, mem_mhz: 0 }, Goal::Balanced, 1.0);
|
||||
let mut rows = vec![synthetic(plan.next(&[]).unwrap().point)];
|
||||
// the first probe (memory up) draws a mismatched hash: refused
|
||||
let probe = plan.next(&rows).unwrap();
|
||||
assert!(probe.point.mem_mhz > 13801, "memory first: {:?}", probe.point);
|
||||
let mut bad = synthetic(probe.point);
|
||||
bad.faults = 1;
|
||||
bad.mark = Some(Mark::Faulted);
|
||||
rows.push(bad);
|
||||
// from here every probe leaves the memory clock alone
|
||||
while let Some(step) = plan.next(&rows) {
|
||||
assert_eq!(step.point.mem_mhz, 0, "memory backed off: {:?}", step.point);
|
||||
rows.push(synthetic(step.point));
|
||||
}
|
||||
assert!(rows.len() >= 3 && rows.len() <= 5);
|
||||
assert!(choose(&rows, 1.0).unwrap().point.mem_mhz == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_goal_picks_its_point() {
|
||||
let l = l5090();
|
||||
let p = |c: u32, m: u32| Point { clock_mhz: c, power_pct: 100, mem_mhz: m };
|
||||
// three points: the fastest (slightly less efficient), the most efficient (4% slower), and the start
|
||||
let rows = vec![synthetic(p(0, 0)), synthetic(p(0, 14001)), synthetic(p(1854, 0))];
|
||||
let eff_plan = Plan::climb(&l, p(0, 0), Goal::Efficiency, 1.0);
|
||||
let bal_plan = Plan::climb(&l, p(0, 0), Goal::Balanced, 1.0);
|
||||
let rate_plan = Plan::climb(&l, p(0, 0), Goal::MaxRate, 1.0);
|
||||
assert_eq!(eff_plan.tolerance_pct, 10.0);
|
||||
assert_eq!(bal_plan.tolerance_pct, 1.0);
|
||||
assert_eq!(rate_plan.tolerance_pct, 0.0);
|
||||
let by = |plan: &Plan| rows.iter().filter(|r| r.usable() && r.mhs >= rows.iter().map(|x| x.mhs).fold(0.0, f64::max) * (1.0 - plan.tolerance_pct / 100.0)).max_by(|a, b| plan.score(a).partial_cmp(&plan.score(b)).unwrap()).unwrap().point;
|
||||
assert_eq!(by(&rate_plan), p(0, 14001), "maximum rate takes the fastest point");
|
||||
assert_eq!(by(&eff_plan), p(1854, 0), "efficiency takes the most MH/W inside 10%");
|
||||
assert_eq!(by(&bal_plan), p(0, 14001), "balanced keeps within 1% of the top rate");
|
||||
assert_eq!(Goal::parse("efficiency"), Goal::Efficiency);
|
||||
assert_eq!(Goal::parse("rate"), Goal::MaxRate);
|
||||
assert_eq!(Goal::parse("anything"), Goal::Balanced);
|
||||
assert!((pounds_per_day(310.0, 28.5) - 2.1204).abs() < 1e-3, "310 W a day at 28.5 p/kWh = £2.12");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_reasons_per_vendor() {
|
||||
let l = l5090();
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ pub enum Cmd {
|
|||
SweepHelperDone(Result<(), String>),
|
||||
/// a tune request (by number) was carried out: what the vendor tool printed, or why it refused
|
||||
TuneSet(u64, Result<String, String>),
|
||||
/// POST /api/tune-progress from a measurement engine beside this app: the card's live tune state
|
||||
TuneProgress(Value),
|
||||
/// Ember 2: Settings > goal, electricity price, the hill-climb switch
|
||||
TuneGoal(Option<String>, Option<f64>, Option<bool>),
|
||||
/// restart the node with the verifier decided again (src/verifier.rs): the trust setting changed, or the
|
||||
/// prover found a host that was not there when the node started
|
||||
RestartNode(String),
|
||||
|
|
@ -119,7 +123,7 @@ impl Shared {
|
|||
st.mining.accepted_total = settings.accepted_total;
|
||||
st.mining.fee_total = settings.fee_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, remote_jobs: settings.remote_jobs, prove: settings.prove, sweep: settings.sweep, power_control: settings.power_control, power_note: String::new(), tuning_off: false, tuning_note: String::new(), dev_fee: settings.dev_fee, proof_verify_trust: settings.proof_verify_trust };
|
||||
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, prove: settings.prove, sweep: settings.sweep, power_control: settings.power_control, power_note: String::new(), tuning_off: false, tuning_note: String::new(), tune_goal: settings.tune_goal.clone(), power_price_pence: settings.power_price_pence, tune_climb: settings.tune_climb, tune_period_s: crate::ember::PERIOD_S, dev_fee: settings.dev_fee, proof_verify_trust: settings.proof_verify_trust };
|
||||
st.dev_fee = crate::state::DevFeeState { on: settings.dev_fee, percent: if settings.dev_fee { 1 } else { 0 }, address: String::new(), line: String::new() };
|
||||
st.live_page = packaged.live_page.clone();
|
||||
st.finality.message = "waiting for the miner".into();
|
||||
|
|
@ -1142,6 +1146,29 @@ impl Engine {
|
|||
self.shared.log("sweep helper: exited");
|
||||
}
|
||||
}
|
||||
Cmd::TuneProgress(v) => self.tune_progress(&v),
|
||||
Cmd::TuneGoal(goal, price, climb) => {
|
||||
{
|
||||
let mut s = self.shared.settings.lock().unwrap();
|
||||
if let Some(g) = &goal {
|
||||
s.tune_goal = g.clone();
|
||||
}
|
||||
if let Some(p) = price {
|
||||
s.power_price_pence = p;
|
||||
}
|
||||
if let Some(c) = climb {
|
||||
s.tune_climb = c;
|
||||
}
|
||||
}
|
||||
self.shared.save_settings();
|
||||
let s = self.shared.settings.lock().unwrap().clone();
|
||||
let mut st = self.st();
|
||||
st.settings.tune_goal = s.tune_goal.clone();
|
||||
st.settings.power_price_pence = s.power_price_pence;
|
||||
st.settings.tune_climb = s.tune_climb;
|
||||
drop(st);
|
||||
self.shared.event("info", &format!("tune goal: {}{}{}", s.tune_goal, if s.power_price_pence > 0.0 { format!(", electricity {:.1} p/kWh", s.power_price_pence) } else { String::new() }, if s.tune_climb { ", hill-climb on" } else { "" }));
|
||||
}
|
||||
Cmd::TuneSet(seq, r) => match r {
|
||||
Ok(text) => {
|
||||
self.tune_acked = Some(seq);
|
||||
|
|
@ -1790,7 +1817,7 @@ impl Engine {
|
|||
// once, ever (src/powertask.rs): a registered task sets the caps with no prompt; the readback judges it
|
||||
if cfg!(windows) && self.power_task_registered() {
|
||||
let pairs: Vec<(String, u64)> = self.st().mining.cards.iter().filter(|c| c.vendor == "nvidia" && c.enabled && c.present() && c.power_default_w > 0.0 && !c.power_applied).map(|c| (c.device.clone(), requested_watts(c).round() as u64)).collect();
|
||||
let dir = self.sweep_dir();
|
||||
let dir = crate::powertask::helper_dir();
|
||||
let shared = self.shared.clone();
|
||||
self.shared.log("power cap: through the Igneum Power Helper task (no prompt)");
|
||||
std::thread::spawn(move || {
|
||||
|
|
@ -1818,7 +1845,8 @@ impl Engine {
|
|||
let dir = self.sweep_dir();
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let script = dir.join("register-power-task.ps1");
|
||||
match std::env::current_exe().map(|exe| std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), crate::powertask::register_script(&exe).as_bytes()].concat())) {
|
||||
let installed: Vec<PathBuf> = crate::powertask::install_candidates();
|
||||
match std::env::current_exe().map(|exe| std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), crate::powertask::register_script(&crate::powertask::task_exe(&exe, &installed)).as_bytes()].concat())) {
|
||||
Ok(Ok(())) => format!("{} & \"{}\" -NoProfile -ExecutionPolicy Bypass -File \"{}\"", cmds.join(" & "), crate::platform::tool("powershell").display(), script.display()),
|
||||
_ => cmds.join(" & "),
|
||||
}
|
||||
|
|
@ -1908,8 +1936,9 @@ impl Engine {
|
|||
}
|
||||
if cfg!(windows) && self.power_task_registered() {
|
||||
// the kill switch: the task unregisters itself (elevated) and exits; nothing is left behind
|
||||
let dir = self.sweep_dir();
|
||||
let _ = std::fs::write(dir.join("cmd.txt"), "remove\n");
|
||||
let hdir = crate::powertask::helper_dir();
|
||||
let _ = std::fs::create_dir_all(&hdir);
|
||||
let _ = std::fs::write(hdir.join("cmd.txt"), "remove\n");
|
||||
match crate::powertask::start() {
|
||||
Ok(()) => self.shared.log("power control off: the Igneum Power Helper task removes itself"),
|
||||
Err(e) => self.shared.log(&format!("power control off: the task could not be started to remove itself ({e}); remove it in Task Scheduler")),
|
||||
|
|
@ -2114,6 +2143,16 @@ impl Engine {
|
|||
self.shared.runtime.app_dir.join("sweep")
|
||||
}
|
||||
|
||||
/// Where this engine's helper reads its commands: the task's own folder when the task is the helper, else this
|
||||
/// engine's sweep folder (the prompted helper script was handed that path on its command line).
|
||||
fn helper_cmd_dir(&self) -> PathBuf {
|
||||
if self.sweep_helper_is_task {
|
||||
crate::powertask::helper_dir()
|
||||
} else {
|
||||
self.sweep_dir()
|
||||
}
|
||||
}
|
||||
|
||||
/// The manifest's tuning object (<app data>/tuning.json, written by the updater from the signed manifest), or
|
||||
/// None: the Ember settings (kill switch, thresholds) and the fleet priors live under it.
|
||||
fn tuning_object(&self) -> Option<Value> {
|
||||
|
|
@ -2148,6 +2187,7 @@ impl Engine {
|
|||
let mut st = self.st();
|
||||
st.settings.tuning_off = !ember.enabled;
|
||||
st.settings.tuning_note = if ember.enabled { String::new() } else { "tuning paused fleet-wide by the signed manifest".into() };
|
||||
st.settings.tune_period_s = ember.period_s;
|
||||
}
|
||||
if paused || !ember.enabled {
|
||||
return;
|
||||
|
|
@ -2235,16 +2275,19 @@ impl Engine {
|
|||
let current = if c.power_limit_w > 0.0 { c.power_limit_w } else { c.power_default_w }.round() as u64;
|
||||
let known_direct = self.sweep_direct;
|
||||
std::thread::spawn(move || {
|
||||
let q = crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "--query-gpu=clocks.max.gr,driver_version", "--format=csv,noheader,nounits"]), None, Duration::from_secs(10)).unwrap_or_default();
|
||||
let q = crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "--query-gpu=clocks.max.gr,driver_version,clocks.mem,clocks.max.mem", "--format=csv,noheader,nounits"]), None, Duration::from_secs(10)).unwrap_or_default();
|
||||
let p: Vec<&str> = q.trim().split(',').map(|s| s.trim()).collect();
|
||||
let clock_max = p.first().and_then(|s| s.parse::<f64>().ok()).unwrap_or(0.0) as u32;
|
||||
let driver = p.get(1).map(|s| s.to_string()).unwrap_or_default();
|
||||
// Ember 2: the memory clock now (the default under load) and the vendor's maximum
|
||||
let mem_default = p.get(2).and_then(|s| s.parse::<f64>().ok()).unwrap_or(0.0) as u32;
|
||||
let mem_max = p.get(3).and_then(|s| s.parse::<f64>().ok()).unwrap_or(0.0) as u32;
|
||||
let direct = match known_direct {
|
||||
Some(d) => Some(d),
|
||||
None if allowed || current > 0 => crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "-pl", ¤t.to_string()]), None, Duration::from_secs(20)).map(|out| out.contains("All done")),
|
||||
None => Some(false),
|
||||
};
|
||||
shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: clock_max, clock_min_mhz: 0, driver, direct: direct.unwrap_or(false), amd_ordinal: -1, ..Default::default() })));
|
||||
shared.send(Cmd::TuneProbe(idx, Ok(TuneProbe { clock_max_mhz: clock_max, clock_min_mhz: 0, driver, direct: direct.unwrap_or(false), amd_ordinal: -1, mem_default_mhz: mem_default, mem_max_mhz: mem_max, ..Default::default() })));
|
||||
});
|
||||
}
|
||||
"amd" => {
|
||||
|
|
@ -2263,7 +2306,7 @@ impl Engine {
|
|||
// the stock clock, not MHz; a range with a negative floor is an offset range and the clock
|
||||
// knob stays closed until the stock clock is known (the power limit is the AMD lever), and
|
||||
// `plimit_range -30 10` bounds the power ladder (the percent scale rides power_* below)
|
||||
Some(t) if t.ok => TuneProbe { clock_max_mhz: if t.gmax_min >= 0.0 && t.gmax_max > 0.0 { t.gmax_max as u32 } else { 0 }, clock_min_mhz: if t.gmax_min > 0.0 { t.gmax_min as u32 } else { 0 }, driver, direct: true, amd_ordinal: t.ordinal as i64, plimit_min: t.plimit_min, plimit_max: t.plimit_max },
|
||||
Some(t) if t.ok => TuneProbe { clock_max_mhz: if t.gmax_min >= 0.0 && t.gmax_max > 0.0 { t.gmax_max as u32 } else { 0 }, clock_min_mhz: if t.gmax_min > 0.0 { t.gmax_min as u32 } else { 0 }, driver, direct: true, amd_ordinal: t.ordinal as i64, plimit_min: t.plimit_min, plimit_max: t.plimit_max, mem_default_mhz: 0, mem_max_mhz: 0 },
|
||||
Some(t) => TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver, direct: false, amd_ordinal: t.ordinal as i64, ..Default::default() },
|
||||
None => TuneProbe { clock_max_mhz: 0, clock_min_mhz: 0, driver, direct: false, amd_ordinal: -1, ..Default::default() },
|
||||
})));
|
||||
|
|
@ -2295,13 +2338,15 @@ impl Engine {
|
|||
};
|
||||
// NVIDIA control: this process is elevated (direct), or Power control is on so the one-prompt helper may run.
|
||||
// The --sweep job alone never counts: it must not raise a prompt on a PC with nobody there (5 October 2026).
|
||||
let power_control = probe.direct || self.shared.settings.lock().unwrap().power_control;
|
||||
// a registered Igneum Power Helper task (src/powertask.rs) is permission too: it sets limits with no prompt,
|
||||
// so an unelevated measurement engine (Power control forced off) still controls NVIDIA through it
|
||||
let power_control = probe.direct || self.shared.settings.lock().unwrap().power_control || (cfg!(windows) && self.power_task_registered());
|
||||
// AMD's power limit is a percent offset from the default (ADLX): the plan's watts scale becomes a percent
|
||||
// scale (default 100, floor 100 + plimit_min, ceiling 100 + plimit_max), tune_apply sends pct - 100
|
||||
let limits = if c.vendor == "amd" && probe.direct && probe.plimit_max >= probe.plimit_min && probe.plimit_min > -100.0 {
|
||||
crate::ember::Limits { power_default_w: 100.0, power_min_w: 100.0 + probe.plimit_min, power_max_w: 100.0 + probe.plimit_max, clock_max_mhz: probe.clock_max_mhz, clock_min_mhz: probe.clock_min_mhz }
|
||||
crate::ember::Limits { power_default_w: 100.0, power_min_w: 100.0 + probe.plimit_min, power_max_w: 100.0 + probe.plimit_max, clock_max_mhz: probe.clock_max_mhz, clock_min_mhz: probe.clock_min_mhz, mem_default_mhz: 0, mem_max_mhz: 0 }
|
||||
} else {
|
||||
crate::ember::Limits { power_default_w: c.power_default_w, power_min_w: c.power_min_w, power_max_w: c.power_max_w, clock_max_mhz: probe.clock_max_mhz, clock_min_mhz: probe.clock_min_mhz }
|
||||
crate::ember::Limits { power_default_w: c.power_default_w, power_min_w: c.power_min_w, power_max_w: c.power_max_w, clock_max_mhz: probe.clock_max_mhz, clock_min_mhz: probe.clock_min_mhz, mem_default_mhz: probe.mem_default_mhz, mem_max_mhz: probe.mem_max_mhz }
|
||||
};
|
||||
let control = match c.vendor.as_str() {
|
||||
"nvidia" => crate::ember::control_reason("nvidia", &limits, &c.device, power_control, false),
|
||||
|
|
@ -2310,6 +2355,26 @@ impl Engine {
|
|||
};
|
||||
if c.vendor == "nvidia" {
|
||||
self.sweep_direct = Some(probe.direct);
|
||||
// the approved step that made this engine elevated registers the Igneum Power Helper here too (run 5,
|
||||
// 6 October 2026: a --sweep engine skipped the cap path where the registration lived, so the one
|
||||
// approved click registered nothing); no prompt: this process already holds the rights
|
||||
if probe.direct && cfg!(windows) && !self.power_task_registered() {
|
||||
let dir = self.sweep_dir();
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let script = dir.join("register-power-task.ps1");
|
||||
let installed: Vec<PathBuf> = crate::powertask::install_candidates();
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
let target = crate::powertask::task_exe(&exe, &installed);
|
||||
if std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), crate::powertask::register_script(&target).as_bytes()].concat()).is_ok() {
|
||||
let mut p = std::process::Command::new(crate::platform::tool("powershell"));
|
||||
p.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", &script.display().to_string()]);
|
||||
crate::platform::quiet(&mut p);
|
||||
let ok = p.status().map(|s| s.success()).unwrap_or(false);
|
||||
self.power_task = None;
|
||||
self.sweep_say(&format!("TUNE helper registered={} action={}", ok, target.display()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut st = self.st();
|
||||
|
|
@ -2327,16 +2392,24 @@ impl Engine {
|
|||
}
|
||||
let tuning = self.tuning_object();
|
||||
let ember = crate::ember::settings_of(tuning.as_ref());
|
||||
let before = crate::ember::Point { clock_mhz: c.clock_cap_mhz, power_pct: if c.power_pct == 0 { 80 } else { c.power_pct } };
|
||||
let before = crate::ember::Point { clock_mhz: c.clock_cap_mhz, power_pct: if c.power_pct == 0 { 80 } else { c.power_pct }, mem_mhz: c.mem_cap_mhz };
|
||||
let before_w = if c.power_limit_w > 0.0 { c.power_limit_w } else { requested_watts(&c) };
|
||||
let key = crate::ember::prior_key(&c.name, &probe.driver, &c.program_class);
|
||||
let prior = crate::ember::prior_of(tuning.as_ref(), &key, ember.min_samples);
|
||||
let full_due = self.tune_full_due.remove(&idx);
|
||||
let (goal, climb_on) = {
|
||||
let s = self.shared.settings.lock().unwrap();
|
||||
(crate::ember::Goal::parse(&s.tune_goal), s.tune_climb)
|
||||
};
|
||||
let plan = if let Some(why) = control.as_ref() {
|
||||
if let Some(cc) = self.st().mining.cards.get_mut(idx) {
|
||||
cc.sweep_note = why.clone();
|
||||
}
|
||||
crate::ember::Plan::baseline(&limits, before, ember.tolerance_pct)
|
||||
} else if climb_on || prior.as_ref().map(|p| p.point.mem_mhz > 0).unwrap_or(false) {
|
||||
// Ember 2: the hill-climb from the fleet prior (or the card's point) toward the goal
|
||||
let start = prior.as_ref().map(|p| p.point).unwrap_or(before);
|
||||
crate::ember::Plan::climb(&limits, start, goal, ember.tolerance_pct)
|
||||
} else if let (Some(p), false, 0) = (prior.as_ref(), full_due, c.sweep_pct) {
|
||||
crate::ember::Plan::confirm(&limits, p.point, before, ember.tolerance_pct)
|
||||
} else {
|
||||
|
|
@ -2380,6 +2453,7 @@ impl Engine {
|
|||
crate::ember::PlanKind::Full => format!("{steps} steps over the power limit and the core clock, {} s each on the live program", (run.timing.settle + run.timing.hold).as_secs()),
|
||||
crate::ember::PlanKind::Confirm => format!("the fleet prior ({} samples) and one neighbour, {} s each", prior.as_ref().map(|p| p.samples).unwrap_or(0), (run.timing.settle + run.timing.hold).as_secs()),
|
||||
crate::ember::PlanKind::Baseline => format!("measuring the card as it runs ({})", control.clone().unwrap_or_default()),
|
||||
crate::ember::PlanKind::Climb => format!("the hill-climb: memory up, core down, up to {steps} probes of {} s", (run.timing.settle + run.timing.hold).as_secs()),
|
||||
};
|
||||
self.shared.event("info", &format!("{}: tuning started: {what}", c.name));
|
||||
self.sweep = Some(run);
|
||||
|
|
@ -2388,7 +2462,8 @@ impl Engine {
|
|||
|
||||
/// The elevated helper (src/sweep.rs helper_script_*): one administrator prompt; it polls <app>/sweep/cmd.txt.
|
||||
fn sweep_helper_start(&mut self, c: &CardState) -> Result<(), String> {
|
||||
if !self.shared.settings.lock().unwrap().power_control {
|
||||
let task = cfg!(windows) && self.power_task_registered();
|
||||
if !task && !self.shared.settings.lock().unwrap().power_control {
|
||||
return Err("Power control is off in Settings".into());
|
||||
}
|
||||
let dir = self.sweep_dir();
|
||||
|
|
@ -2406,8 +2481,11 @@ impl Engine {
|
|||
format!("sh \"{}\" \"{}\" \"{}\" {} {}", script.display(), dir.display(), smi, c.device, restore)
|
||||
};
|
||||
if cfg!(windows) && self.power_task_registered() {
|
||||
// once, ever: the registered task is the helper; it reads the same command file, no prompt
|
||||
let _ = std::fs::write(dir.join("cmd.txt"), format!("{} dev {}\n", crate::platform::unix_now() % 1_000_000, c.device));
|
||||
// once, ever: the registered task is the helper; it reads ITS command file (powertask::helper_dir, never
|
||||
// under a scratch IGNEUM_APP_DATA), no prompt
|
||||
let hdir = crate::powertask::helper_dir();
|
||||
let _ = std::fs::create_dir_all(&hdir);
|
||||
let _ = std::fs::write(hdir.join("cmd.txt"), format!("{} dev {}\n", crate::platform::unix_now() % 1_000_000, c.device));
|
||||
crate::powertask::start()?;
|
||||
self.shared.log("tune helper: the Igneum Power Helper task (no prompt)");
|
||||
self.sweep_helper = true;
|
||||
|
|
@ -2427,7 +2505,7 @@ impl Engine {
|
|||
|
||||
fn sweep_helper_quit(&mut self) {
|
||||
if self.sweep_helper {
|
||||
let _ = std::fs::write(self.sweep_dir().join("cmd.txt"), "quit\n");
|
||||
let _ = std::fs::write(self.helper_cmd_dir().join("cmd.txt"), "quit\n");
|
||||
if self.sweep_helper_is_task {
|
||||
// the task exits on quit and reports nothing back; the next tune starts it again
|
||||
self.sweep_helper = false;
|
||||
|
|
@ -2446,6 +2524,7 @@ impl Engine {
|
|||
let Some(c) = card else { return };
|
||||
let w = step.watts.round() as u64;
|
||||
let clock = step.point.clock_mhz;
|
||||
let mem = step.point.mem_mhz;
|
||||
let shared = self.shared.clone();
|
||||
self.tune_acked = None;
|
||||
match c.vendor.as_str() {
|
||||
|
|
@ -2469,6 +2548,15 @@ impl Engine {
|
|||
ok &= out.contains("All done") || out.to_ascii_lowercase().contains("clocks set") || out.to_ascii_lowercase().contains("reset");
|
||||
text.push(' ');
|
||||
text.push_str(out.trim());
|
||||
// Ember 2: the memory clock, locked to one value (`-lmc m,m`) or reset (`-rmc`)
|
||||
let out = if mem > 0 {
|
||||
crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "-lmc", &format!("{mem},{mem}")]), None, Duration::from_secs(20)).unwrap_or_else(|| "nvidia-smi did not answer".into())
|
||||
} else {
|
||||
crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "-rmc"]), None, Duration::from_secs(20)).unwrap_or_else(|| "nvidia-smi did not answer".into())
|
||||
};
|
||||
ok &= out.contains("All done") || out.to_ascii_lowercase().contains("clocks set") || out.to_ascii_lowercase().contains("reset") || out.to_ascii_lowercase().contains("not supported");
|
||||
text.push(' ');
|
||||
text.push_str(out.trim());
|
||||
shared.send(Cmd::TuneSet(seq, if ok { Ok(text) } else { Err(text) }));
|
||||
});
|
||||
}
|
||||
|
|
@ -2478,8 +2566,8 @@ impl Engine {
|
|||
return;
|
||||
}
|
||||
let dev = device.to_string();
|
||||
let cmd = format!("{seq}0 dev {dev}\n{seq}1 pl {w}\n{seq}2 {}\n", if clock > 0 { format!("lgc {clock}") } else { "rgc".to_string() });
|
||||
let _ = std::fs::write(self.sweep_dir().join("cmd.txt"), cmd);
|
||||
let cmd = format!("{seq}0 dev {dev}\n{seq}1 pl {w}\n{seq}2 {}\n{seq}3 {}\n", if clock > 0 { format!("lgc {clock}") } else { "rgc".to_string() }, if mem > 0 { format!("lmc {mem}") } else { "rmc".to_string() });
|
||||
let _ = std::fs::write(self.helper_cmd_dir().join("cmd.txt"), cmd);
|
||||
// the helper polls twice a second and nvidia-smi answers within a second or two
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(Duration::from_secs(4));
|
||||
|
|
@ -2520,7 +2608,50 @@ impl Engine {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
self.shared.log(&format!("tune: {} MHz, {}% ({w} W) requested on device {device} (request {seq})", if clock > 0 { clock.to_string() } else { "unlocked".into() }, step.point.power_pct));
|
||||
self.shared.log(&format!("tune: {} MHz, {}% ({w} W), memory {} requested on device {device} (request {seq})", if clock > 0 { clock.to_string() } else { "unlocked".into() }, step.point.power_pct, if mem > 0 { format!("{mem} MHz") } else { "default".into() }));
|
||||
}
|
||||
|
||||
/// A measurement engine's progress for one card (POST /api/tune-progress, forwarded by the job playbook from the
|
||||
/// engine's `TUNE progress` lines): the installed app shows the step, the live rate and draw and the time left
|
||||
/// instead of a bare "off" while a job holds its miners. `done` ends it: the row reads the tuned line.
|
||||
fn tune_progress(&mut self, v: &Value) {
|
||||
let key = v.get("key").and_then(|k| k.as_str()).unwrap_or("");
|
||||
let mut st = self.st();
|
||||
let Some(c) = st.mining.cards.iter_mut().find(|c| c.key == key || (!key.is_empty() && c.name.replace(' ', "_") == key)) else { return };
|
||||
let n = |k: &str| v.get(k).and_then(|x| x.as_f64()).unwrap_or(0.0);
|
||||
if v.get("done").and_then(|d| d.as_bool()).unwrap_or(false) {
|
||||
c.sweep_state = "idle".into();
|
||||
c.state = if self.job_hold { "held".into() } else { c.state.clone() };
|
||||
c.tune_step = 0;
|
||||
c.tune_steps = 0;
|
||||
c.tune_eta_s = 0;
|
||||
if n("mhs") > 0.0 && n("watts") > 0.0 {
|
||||
c.sweep_mhs = n("mhs");
|
||||
c.sweep_watts = n("watts");
|
||||
c.sweep_eff = n("mhs") / n("watts");
|
||||
c.sweep_pct = n("power_pct") as u32;
|
||||
c.tune_clock_mhz = n("clock_mhz") as u32;
|
||||
c.tune_source = v.get("plan").and_then(|p| p.as_str()).unwrap_or("full").into();
|
||||
c.tune_line = crate::ember::tuned_line(n("mhs"), n("watts"), n("mhs") / n("watts"));
|
||||
c.sweep_at = crate::platform::unix_now_f();
|
||||
}
|
||||
c.sweep_note = v.get("note").and_then(|x| x.as_str()).unwrap_or("").to_string();
|
||||
return;
|
||||
}
|
||||
c.state = "tuning".into();
|
||||
c.sweep_state = "running".into();
|
||||
c.sweep_note = v.get("note").and_then(|x| x.as_str()).unwrap_or("tuning").to_string();
|
||||
c.tune_step = n("step") as u32;
|
||||
c.tune_steps = n("of") as u32;
|
||||
c.tune_eta_s = n("eta_s") as i64;
|
||||
c.tune_plan = v.get("plan").and_then(|p| p.as_str()).unwrap_or("").into();
|
||||
if n("mhs") > 0.0 {
|
||||
c.hash_now = n("mhs");
|
||||
}
|
||||
if n("watts") > 0.0 {
|
||||
c.power_w = n("watts");
|
||||
}
|
||||
c.message = String::new();
|
||||
}
|
||||
|
||||
/// Faults and conditions first, then the state machine.
|
||||
|
|
@ -2538,7 +2669,7 @@ impl Engine {
|
|||
Some("a remote job took the GPU".into())
|
||||
} else if self.st().mining.paused {
|
||||
Some("mining paused".into())
|
||||
} else if c.state != "mining" {
|
||||
} else if c.state != "mining" && c.state != "tuning" {
|
||||
Some(format!("the card left mining ({})", if c.state == "starting" || c.state == "restarting" { "worker restart, likely the hour boundary" } else { &c.state }))
|
||||
} else if slot_error {
|
||||
Some("the worker reported an error".into())
|
||||
|
|
@ -2587,9 +2718,20 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let Some(words) = self.sweep.as_ref().map(|r| r.words(now)) {
|
||||
if let Some((words, step, of, eta, plan)) = self.sweep.as_ref().map(|r| (r.words(now), r.rows.len() as u32 + 1, r.plan.len() as u32, r.eta_s(now), r.plan.kind.name())) {
|
||||
let (key, mhs, watts) = self.st().mining.cards.get(idx).map(|c| (c.key.clone(), c.hash_now, c.power_w)).unwrap_or_default();
|
||||
if let Some(cc) = self.st().mining.cards.get_mut(idx) {
|
||||
cc.sweep_note = words;
|
||||
cc.sweep_note = words.clone();
|
||||
cc.state = if cc.state == "mining" { "tuning".into() } else { cc.state.clone() };
|
||||
cc.tune_step = step;
|
||||
cc.tune_steps = of;
|
||||
cc.tune_eta_s = eta;
|
||||
cc.tune_plan = plan.into();
|
||||
}
|
||||
if self.shared.runtime.sweep_only {
|
||||
// the job playbook forwards this to the installed app's /api/tune-progress
|
||||
println!("TUNE progress key={} step={step} of={of} eta_s={eta} mhs={mhs:.2} watts={watts:.1} plan={plan} note={}", key.replace(' ', "_"), words.replace(' ', "_"));
|
||||
let _ = std::io::stdout().flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2612,9 +2754,17 @@ impl Engine {
|
|||
c.sweep_mhs = row.mhs;
|
||||
c.sweep_at = unix as f64;
|
||||
c.sweep_state = "idle".into();
|
||||
if c.state == "tuning" {
|
||||
c.state = "mining".into();
|
||||
}
|
||||
c.tune_step = 0;
|
||||
c.tune_steps = 0;
|
||||
c.tune_eta_s = 0;
|
||||
c.tune_clock_mhz = row.point.clock_mhz;
|
||||
c.tune_mem_mhz = row.point.mem_mhz;
|
||||
c.tune_source = kind.name().into();
|
||||
c.tune_line = crate::ember::tuned_line(row.mhs, row.watts, row.eff);
|
||||
c.tune_line = crate::ember::result_line(kind, row.mhs, row.watts, row.eff);
|
||||
c.tune_curve = run.rows.iter().map(|r| r.json()).collect();
|
||||
let control = c.tune_control;
|
||||
if kind == crate::ember::PlanKind::Baseline {
|
||||
c.sweep_note = if control { String::new() } else { c.sweep_note.clone() };
|
||||
|
|
@ -2623,6 +2773,7 @@ impl Engine {
|
|||
} else {
|
||||
c.power_pct = row.point.power_pct;
|
||||
c.clock_cap_mhz = row.point.clock_mhz;
|
||||
c.mem_cap_mhz = row.point.mem_mhz;
|
||||
if c.vendor == "nvidia" {
|
||||
c.power_limit_w = row.limit;
|
||||
c.power_applied = true;
|
||||
|
|
@ -2637,6 +2788,7 @@ impl Engine {
|
|||
e.sweep_watts = row.watts;
|
||||
e.sweep_mhs = row.mhs;
|
||||
e.sweep_clock_mhz = row.point.clock_mhz;
|
||||
e.sweep_mem_mhz = row.point.mem_mhz;
|
||||
e.sweep_driver = c.driver.clone();
|
||||
e.sweep_class = c.program_class.clone();
|
||||
e.sweep_source = kind.name().into();
|
||||
|
|
@ -2647,18 +2799,21 @@ impl Engine {
|
|||
};
|
||||
let _ = key;
|
||||
self.shared.save_settings();
|
||||
self.sweep_say(&format!("TUNE chosen card={} clock={} cap={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} plan={}{}", run.label, row.point.clock_mhz, row.point.power_pct, row.limit, row.watts, row.mhs, row.eff, kind.name(), if pinned { " pinned=1" } else { "" }));
|
||||
// 6 October 2026, run 6: the 5090's chosen clock sat on the ladder's floor (1,854 MHz = the old 60%), so a
|
||||
// chosen point at the floor says so: it is the lowest step measured, not the optimum (Ember 2's climb walks on)
|
||||
let floor = row.point.clock_mhz > 0 && row.point.clock_mhz <= run.plan.limits.clock_floor();
|
||||
self.sweep_say(&format!("TUNE chosen card={} clock={} cap={} mem={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} plan={}{}{}", run.label, row.point.clock_mhz, row.point.power_pct, row.point.mem_mhz, row.limit, row.watts, row.mhs, row.eff, kind.name(), if pinned { " pinned=1" } else { "" }, if floor { " floor=1 note=floor,_not_optimum" } else { "" }));
|
||||
let before = run.rows.first().filter(|_| kind == crate::ember::PlanKind::Full).cloned();
|
||||
let record = crate::ember::record_json(crate::platform::unix_now_f(), &crate::config::fingerprint8(&self.shared.runtime.machine_id), VERSION, crate::manifest::platform_name(), &name, &vendor, &driver, &class, kind, &run.rows, Some(&row), before.as_ref());
|
||||
let record = crate::ember::record_json(crate::platform::unix_now_f(), &crate::config::fingerprint8(&self.shared.runtime.machine_id), VERSION, crate::manifest::platform_name(), &name, &vendor, &driver, &class, kind, &run.rows, Some(&row), before.as_ref(), floor);
|
||||
self.sweep_say(&format!("TUNE {record}"));
|
||||
let line = crate::ember::tuned_line(row.mhs, row.watts, row.eff);
|
||||
let line = crate::ember::result_line(kind, row.mhs, row.watts, row.eff);
|
||||
match kind {
|
||||
crate::ember::PlanKind::Baseline => self.shared.event("info", &format!("{name}: measured as it runs: {line}{}", if control { String::new() } else { " (measure only; no control on this card)".into() })),
|
||||
crate::ember::PlanKind::Baseline => self.shared.event("info", &format!("{name}: {line}{}", if control { String::new() } else { " (measure only; no control on this card)".into() })),
|
||||
_ if pinned => self.shared.event("ok", &format!("{name}: best point {} ({line}); your setting stays pinned", point_words(&row.point))),
|
||||
_ => self.shared.event("ok", &format!("{name}: {line}, {} held", point_words(&row.point))),
|
||||
}
|
||||
if pinned && kind != crate::ember::PlanKind::Baseline {
|
||||
let p = self.st().mining.cards.get(idx).map(|c| crate::ember::Point { clock_mhz: c.clock_cap_mhz, power_pct: c.power_pct }).unwrap_or(run.before);
|
||||
let p = self.st().mining.cards.get(idx).map(|c| crate::ember::Point { clock_mhz: c.clock_cap_mhz, power_pct: c.power_pct, mem_mhz: c.mem_cap_mhz }).unwrap_or(run.before);
|
||||
let w = self.st().mining.cards.get(idx).map(requested_watts).unwrap_or(run.before_w);
|
||||
self.tune_apply(idx, &run.device, &crate::ember::Step { point: p, watts: w, kind: crate::ember::Kind::Confirm });
|
||||
}
|
||||
|
|
@ -2686,7 +2841,7 @@ impl Engine {
|
|||
(Some(r), _) => (r.card, r.label.clone(), r.device.clone(), r.before, r.before_w, r.forced, r.seq > 0 && r.plan.kind != crate::ember::PlanKind::Baseline),
|
||||
(None, Some((idx, forced))) => {
|
||||
let c = self.st().mining.cards.get(idx).cloned();
|
||||
let (label, device, w, p) = c.map(|c| (format!("card-{idx}"), c.device.clone(), if c.power_limit_w > 0.0 { c.power_limit_w } else { requested_watts(&c) }, crate::ember::Point { clock_mhz: c.clock_cap_mhz, power_pct: c.power_pct })).unwrap_or_default();
|
||||
let (label, device, w, p) = c.map(|c| (format!("card-{idx}"), c.device.clone(), if c.power_limit_w > 0.0 { c.power_limit_w } else { requested_watts(&c) }, crate::ember::Point { clock_mhz: c.clock_cap_mhz, power_pct: c.power_pct, mem_mhz: c.mem_cap_mhz })).unwrap_or_default();
|
||||
(idx, label, device, p, w, forced, false)
|
||||
}
|
||||
_ => return,
|
||||
|
|
@ -2697,6 +2852,12 @@ impl Engine {
|
|||
match st.mining.cards.get_mut(idx) {
|
||||
Some(c) => {
|
||||
c.sweep_state = "idle".into();
|
||||
if c.state == "tuning" {
|
||||
c.state = "mining".into();
|
||||
}
|
||||
c.tune_step = 0;
|
||||
c.tune_steps = 0;
|
||||
c.tune_eta_s = 0;
|
||||
c.sweep_note = format!("tuning stopped: {why}");
|
||||
if touched {
|
||||
c.power_pct = before.power_pct;
|
||||
|
|
@ -2870,6 +3031,13 @@ impl Engine {
|
|||
}
|
||||
if self.job_hold && !self.jobs.holds_miners() {
|
||||
self.job_hold = false;
|
||||
for c in self.st().mining.cards.iter_mut().filter(|c| c.state == "held" || c.state == "tuning") {
|
||||
c.state = "off".into();
|
||||
c.sweep_state = if c.sweep_state == "running" { "idle".into() } else { c.sweep_state.clone() };
|
||||
c.tune_step = 0;
|
||||
c.tune_steps = 0;
|
||||
c.tune_eta_s = 0;
|
||||
}
|
||||
if self.running {
|
||||
self.shared.event("info", "job finished; the miners restart");
|
||||
for m in self.miners.iter_mut() {
|
||||
|
|
@ -2886,7 +3054,9 @@ impl Engine {
|
|||
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();
|
||||
// never a bare "off" at 0 MH/s: the row says why (a job holds the card)
|
||||
c.state = "held".into();
|
||||
c.message = format!("held for a remote job: {}", short(&why, 80));
|
||||
}
|
||||
self.jobs.miners_stopped(&self.shared);
|
||||
}
|
||||
|
|
@ -3970,6 +4140,9 @@ pub struct TuneProbe {
|
|||
/// AMD: the power offset range in percent from the `tune` line (PC 1's 9070 XT: -30 to 10)
|
||||
pub plimit_min: f64,
|
||||
pub plimit_max: f64,
|
||||
/// Ember 2, NVIDIA: the memory clock under load and the vendor's maximum (nvidia-smi clocks.mem, clocks.max.mem)
|
||||
pub mem_default_mhz: u32,
|
||||
pub mem_max_mhz: u32,
|
||||
}
|
||||
|
||||
/// One `tune` line of igneum-gpu-telemetry --tune:
|
||||
|
|
|
|||
|
|
@ -163,6 +163,8 @@ pub fn apply_pref(c: &mut CardState, p: &CardPref) {
|
|||
c.sweep_at = p.sweep_at as f64;
|
||||
// Ember Tune (src/ember.rs): the clock cap the last tune chose, its plan, and the row's Tuned line
|
||||
c.tune_clock_mhz = p.sweep_clock_mhz;
|
||||
c.tune_mem_mhz = p.sweep_mem_mhz;
|
||||
c.mem_cap_mhz = if p.pinned || p.sweep_source == "baseline" { 0 } else { p.sweep_mem_mhz };
|
||||
c.clock_cap_mhz = if p.pinned || p.sweep_source == "baseline" { 0 } else { p.sweep_clock_mhz };
|
||||
c.tune_source = p.sweep_source.clone();
|
||||
if p.sweep_mhs > 0.0 && p.sweep_watts > 0.0 {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,9 @@ pub struct Manifest {
|
|||
pub node_commit: String,
|
||||
/// the 40-hex commit (manifest node.commit_full, packer of 6 October 2026); empty in older manifests
|
||||
pub node_commit_full: String,
|
||||
/// the commit's author time (manifest node.commit_time, unix seconds): SOURCE_DATE_EPOCH of every build stage, so mimalloc's
|
||||
/// __DATE__/__TIME__ and anything else that reads the clock give one answer per commit (6 October 2026, the 0.3.14 repro)
|
||||
pub node_commit_time: u64,
|
||||
pub node_dirty: bool,
|
||||
pub app_version: String,
|
||||
pub builds: Vec<Unit>,
|
||||
|
|
@ -161,7 +164,7 @@ pub fn parse_manifest(text: &str) -> Result<Manifest, String> {
|
|||
let s = |k: &str| v.get(k).and_then(|x| x.as_str()).unwrap_or("").trim().to_string();
|
||||
let node = v.get("node").cloned().unwrap_or(Value::Null);
|
||||
let ns = |k: &str| node.get(k).and_then(|x| x.as_str()).unwrap_or("").trim().to_string();
|
||||
let mut m = Manifest { created_at: s("created_at"), node_branch: ns("branch"), node_commit: ns("commit"), node_commit_full: ns("commit_full"), node_dirty: node.get("dirty").and_then(|x| x.as_bool()).unwrap_or(false), app_version: s("app_version"), ..Default::default() };
|
||||
let mut m = Manifest { created_at: s("created_at"), node_branch: ns("branch"), node_commit: ns("commit"), node_commit_full: ns("commit_full"), node_commit_time: node.get("commit_time").and_then(|x| x.as_u64()).unwrap_or(0), node_dirty: node.get("dirty").and_then(|x| x.as_bool()).unwrap_or(false), app_version: s("app_version"), ..Default::default() };
|
||||
let builds = v.get("builds").and_then(|b| b.as_array()).ok_or("manifest.json has no \"builds\" list")?;
|
||||
for (i, b) in builds.iter().enumerate() {
|
||||
let dir = b.get("dir").and_then(|x| x.as_str()).unwrap_or("").trim().to_string();
|
||||
|
|
@ -329,6 +332,11 @@ pub fn build_script(p: &BuildParams, job_id: &str, m: &Manifest, target: &str) -
|
|||
s.push_str(&windows_env());
|
||||
}
|
||||
s.push_str(&format!("echo \"STAGE {target} start $(now)\"\n"));
|
||||
if m.node_commit_time > 0 {
|
||||
// reproducible builds: the commit's author time and UTC for every compiler of this stage; the target dir is the one
|
||||
// persistent $CARGO_TARGET_DIR (never a per-run name: prost's generated code embeds OUT_DIR)
|
||||
s.push_str(&format!("export SOURCE_DATE_EPOCH={} TZ=UTC; echo \"SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH TZ=UTC\"\n", m.node_commit_time));
|
||||
}
|
||||
s.push_str(&format!("mkdir -p \"$OUT/{target}\"\n"));
|
||||
s.push_str("rc_all=0\n");
|
||||
// the node's full commit from the manifest (each stage is its own script; the extract stage read it too)
|
||||
|
|
@ -498,7 +506,7 @@ mod tests {
|
|||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
const MANIFEST: &str = r#"{"created_at":"2026-10-04T20:00:00Z","node":{"branch":"devnet-v4","commit":"3bfe346f","dirty":false,"source":"vendor/igneum-node-v4"},"repo":{"commit":"0f44edd","branch":"build-job","dirty":true},"app_version":"0.3.4",
|
||||
const MANIFEST: &str = r#"{"created_at":"2026-10-04T20:00:00Z","node":{"branch":"devnet-v4","commit":"3bfe346f","commit_time":1791300000,"dirty":false,"source":"vendor/igneum-node-v4"},"repo":{"commit":"0f44edd","branch":"build-job","dirty":true},"app_version":"0.3.4",
|
||||
"builds":[{"dir":"node","packages":["kaspad","igneum-miner"],"features":["kaspad/igneum-pow"],"bins":["igneumd","igneum-miner"],"targets":["linux","windows"]},
|
||||
{"dir":"app/igneum-app","packages":["igneum-app"],"bins":["igneum-app"],"optional_on":["linux"]}],
|
||||
"tests":[{"dir":"app/igneum-app","packages":["igneum-app"]},{"dir":"node","packages":["igneum-miner"]},{"dir":"node","packages":[]}]}"#;
|
||||
|
|
@ -577,6 +585,8 @@ mod tests {
|
|||
let lin = build_script(&p, id, &m, "linux");
|
||||
assert!(lin.contains("cargo build --release $JOBS -p kaspad -p igneum-miner --features kaspad/igneum-pow 2>&1"));
|
||||
assert!(lin.contains("cd \"$SRC/app/igneum-app\""));
|
||||
// reproducible builds (6 October 2026): the commit's author time and UTC exported before any cargo build of the stage
|
||||
assert!(lin.contains("export SOURCE_DATE_EPOCH=1791300000 TZ=UTC") && lin.find("SOURCE_DATE_EPOCH=1791300000").unwrap() < lin.find("cargo build").unwrap(), "{lin}");
|
||||
assert!(lin.contains("optional on linux: not fatal"));
|
||||
assert!(!lin.contains("--target x86_64-pc-windows-gnu"));
|
||||
// the windows stage ships the runtime DLLs of its own toolchain next to the exes; the linux stage does not
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ fn main() {
|
|||
if args.iter().any(|a| a == "--power-helper") {
|
||||
// the scheduled task's action (src/powertask.rs): elevated, runs only digit-argument nvidia-smi commands
|
||||
// from <app data>/app/sweep/cmd.txt, exits on quit, remove or 20 idle minutes
|
||||
let dir = powertask::sweep_dir(&platform::data_root().join("app"));
|
||||
let dir = powertask::helper_dir();
|
||||
std::process::exit(powertask::run_helper(&dir));
|
||||
}
|
||||
if args.iter().any(|a| a == "--launch") {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,14 @@ pub fn data_root() -> PathBuf {
|
|||
if let Some(p) = std::env::var_os("IGNEUM_APP_DATA") {
|
||||
return PathBuf::from(p);
|
||||
}
|
||||
fixed_data_root()
|
||||
}
|
||||
|
||||
/// The platform's own root, with IGNEUM_APP_DATA ignored: where the Power Helper task (started by Windows with no
|
||||
/// environment of ours) reads its command file, so a measurement engine under a scratch root (IGNEUM_APP_DATA set)
|
||||
/// writes its task commands here and not under its own root. 6 October 2026: found while planning the unelevated
|
||||
/// climb through the task; the elevated runs never crossed it (they set the limits directly).
|
||||
pub fn fixed_data_root() -> PathBuf {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
home().join("Library").join("Application Support").join("Igneum")
|
||||
|
|
@ -430,11 +438,21 @@ pub fn sync_clock() -> Result<String, String> {
|
|||
|
||||
/// Runs a command line with administrator rights (one prompt): the NVIDIA power cap needs it on Windows.
|
||||
/// Blocking; call from a thread.
|
||||
/// The cmd.exe argument for one elevated line. cmd's documented rule: when the text after /c starts with a quote
|
||||
/// and holds more than two quotes (two cards, or a cap plus a script), it strips the FIRST and LAST quote and runs
|
||||
/// the broken remainder (PC 1, 6 October 2026, 15:45Z: the one approved Power control step ran
|
||||
/// `"...\nvidia-smi.exe" -i 0 -pl 460 & "...\nvidia-smi.exe" -i 1 -pl 160 & "...\powershell.exe" ... -File "...ps1"`,
|
||||
/// exit 1, no cap applied, no task registered; a single-card PC, two quotes, was fine, which is why PC 2 never
|
||||
/// showed it). Wrapping the whole line in one outer pair makes cmd strip exactly those.
|
||||
pub fn cmd_c_args(cmdline: &str) -> String {
|
||||
format!("/c \"{cmdline}\"")
|
||||
}
|
||||
|
||||
pub fn run_elevated(cmdline: &str) -> Result<(), String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let cmd = tool("cmd").display().to_string();
|
||||
let mut c = elevated_command(&cmd, &format!("/c {cmdline}"));
|
||||
let mut c = elevated_command(&cmd, &cmd_c_args(cmdline));
|
||||
let out = c.output().map_err(|e| e.to_string())?;
|
||||
if out.status.success() {
|
||||
Ok(())
|
||||
|
|
@ -511,6 +529,20 @@ pub fn quiet(cmd: &mut Command) -> &mut Command {
|
|||
cmd
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cmd_tests {
|
||||
#[test]
|
||||
fn an_elevated_line_is_wrapped_so_cmd_keeps_every_inner_quote() {
|
||||
let line = r#""C:\WINDOWS\System32\nvidia-smi.exe" -i 0 -pl 460 & "C:\WINDOWS\System32\nvidia-smi.exe" -i 1 -pl 160 & "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -File "C:\x\register-power-task.ps1""#;
|
||||
let a = super::cmd_c_args(line);
|
||||
assert!(a.starts_with("/c \"\"C:\\WINDOWS"), "{a}");
|
||||
assert!(a.ends_with("register-power-task.ps1\"\""), "{a}");
|
||||
// the inner line is intact between the outer pair
|
||||
assert_eq!(&a[4..a.len() - 1], line);
|
||||
assert_eq!(super::cmd_c_args("echo hi"), "/c \"echo hi\"");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod lock_tests {
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -43,8 +43,14 @@ pub enum HelperCmd {
|
|||
PowerLimit(u64),
|
||||
ClockCap(u64),
|
||||
ClockReset,
|
||||
/// Ember 2: the memory clock locked to one value (`-lmc m,m`), or reset (`-rmc`)
|
||||
MemClock(u64),
|
||||
MemReset,
|
||||
Quit,
|
||||
Remove,
|
||||
/// re-point the task at the installed exe (no path argument: the helper finds the install folder itself, so a
|
||||
/// writer of cmd.txt can never choose what runs elevated); 6 October 2026, run 6 registered a scratch copy
|
||||
Reregister,
|
||||
}
|
||||
|
||||
/// Parses one line: `<seq> <verb> [<digits>]` (the 0.3.9 form `<seq> <watts>` reads as a power limit; `quit` and
|
||||
|
|
@ -65,7 +71,10 @@ pub fn parse_line(line: &str) -> Option<(u64, HelperCmd)> {
|
|||
[_, "pl", w] if digits(w) => Some((seq, HelperCmd::PowerLimit(w.parse().ok()?))),
|
||||
[_, "lgc", m] if digits(m) => Some((seq, HelperCmd::ClockCap(m.parse().ok()?))),
|
||||
[_, "rgc"] => Some((seq, HelperCmd::ClockReset)),
|
||||
[_, "lmc", m] if digits(m) => Some((seq, HelperCmd::MemClock(m.parse().ok()?))),
|
||||
[_, "rmc"] => Some((seq, HelperCmd::MemReset)),
|
||||
[_, "dev", d] if digits(d) => Some((seq, HelperCmd::Dev(d.to_string()))),
|
||||
[_, "reregister"] => Some((seq, HelperCmd::Reregister)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -76,10 +85,47 @@ pub fn smi_args(dev: &str, c: &HelperCmd) -> Option<Vec<String>> {
|
|||
HelperCmd::PowerLimit(w) => Some(vec!["-i".into(), dev.into(), "-pl".into(), w.to_string()]),
|
||||
HelperCmd::ClockCap(m) => Some(vec!["-i".into(), dev.into(), "-lgc".into(), format!("0,{m}")]),
|
||||
HelperCmd::ClockReset => Some(vec!["-i".into(), dev.into(), "-rgc".into()]),
|
||||
HelperCmd::MemClock(m) => Some(vec!["-i".into(), dev.into(), "-lmc".into(), format!("{m},{m}")]),
|
||||
HelperCmd::MemReset => Some(vec!["-i".into(), dev.into(), "-rmc".into()]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The executable the task should point at: the running one, unless it runs out of a jobs folder (a measurement
|
||||
/// kit a job fetched: `<app data>\app\jobs\<id>\igneum-app-ember.exe`), in which case the installed app's own
|
||||
/// exe beside it is the durable target (6 October 2026: the first registration on PC 1 came from such a kit).
|
||||
pub fn task_exe(running: &Path, install_candidates: &[PathBuf]) -> PathBuf {
|
||||
// the running exe stands only when it is itself an install candidate (either separator, any case: the path
|
||||
// comes from Windows, the tests run on the Mac too); a kit under jobs\ or a measurement run's scratch copy
|
||||
// (run 6, 6 October 2026: igneum-tune-<stamp>\bin\igneum-app.exe) never becomes the task's action when an
|
||||
// installed exe exists
|
||||
let norm = |p: &Path| p.to_string_lossy().replace('/', "\\").to_ascii_lowercase();
|
||||
let me = norm(running);
|
||||
if install_candidates.iter().any(|p| norm(p) == me) {
|
||||
return running.to_path_buf();
|
||||
}
|
||||
if let Some(p) = install_candidates.iter().find(|p| p.is_file()) {
|
||||
return p.clone();
|
||||
}
|
||||
running.to_path_buf()
|
||||
}
|
||||
|
||||
/// Where the installed exe lives: the per-user install, then the old administrator install.
|
||||
pub fn install_candidates() -> Vec<PathBuf> {
|
||||
[
|
||||
std::env::var_os("LOCALAPPDATA").map(|l| PathBuf::from(l).join("Programs").join("Igneum Miner").join("igneum-app.exe")),
|
||||
std::env::var_os("ProgramFiles").map(|p| PathBuf::from(p).join("Igneum Miner").join("igneum-app.exe")),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The PowerShell that prints the task's action (what `reregister` is proven by).
|
||||
pub fn readback_command() -> String {
|
||||
format!("$t = Get-ScheduledTask -TaskName '{TASK_NAME}' -ErrorAction SilentlyContinue; if ($t) {{ Write-Output ($t.Actions[0].Execute + ' ' + $t.Actions[0].Arguments) }}; exit 0")
|
||||
}
|
||||
|
||||
/// The PowerShell that registers the task (run inside the ONE elevated step, with the caps). `exe` is this
|
||||
/// executable's path in the install folder. Principal: the signed-in user, interactive logon, highest run level; no
|
||||
/// trigger; may start on battery; one hour limit per run; multiple starts are ignored while one runs.
|
||||
|
|
@ -174,6 +220,26 @@ pub fn run_helper(dir: &Path) -> i32 {
|
|||
return if ok { 0 } else { 1 };
|
||||
}
|
||||
_ if seq <= last_seq => continue,
|
||||
HelperCmd::Reregister => {
|
||||
last_seq = seq;
|
||||
idle = Instant::now();
|
||||
let Some(target) = install_candidates().into_iter().find(|p| p.is_file()) else {
|
||||
log(&format!("{seq} reregister: no installed exe found"));
|
||||
continue;
|
||||
};
|
||||
let script = dir.join("register-power-task.ps1");
|
||||
let ok = std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), register_script(&target).as_bytes()].concat()).is_ok() && {
|
||||
let mut p = std::process::Command::new(crate::platform::tool("powershell"));
|
||||
p.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", &script.display().to_string()]);
|
||||
crate::platform::quiet(&mut p);
|
||||
p.status().map(|s| s.success()).unwrap_or(false)
|
||||
};
|
||||
let mut q = std::process::Command::new(crate::platform::tool("powershell"));
|
||||
q.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &readback_command()]);
|
||||
crate::platform::quiet(&mut q);
|
||||
let now = q.output().map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_default();
|
||||
log(&format!("{seq} reregister {}: the task now runs {now}", if ok { "ok" } else { "failed" }));
|
||||
}
|
||||
HelperCmd::Dev(d) => {
|
||||
last_seq = seq;
|
||||
idle = Instant::now();
|
||||
|
|
@ -206,6 +272,12 @@ pub fn sweep_dir(app_dir: &Path) -> PathBuf {
|
|||
app_dir.join("sweep")
|
||||
}
|
||||
|
||||
/// The one folder the task's helper reads: `<fixed data root>/app/sweep` (never under IGNEUM_APP_DATA). Every
|
||||
/// engine that talks to the task writes its command file here; the `--power-helper` entry reads here.
|
||||
pub fn helper_dir() -> PathBuf {
|
||||
sweep_dir(&crate::platform::fixed_data_root().join("app"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -215,10 +287,21 @@ mod tests {
|
|||
assert_eq!(parse_line("7 pl 460"), Some((7, HelperCmd::PowerLimit(460))));
|
||||
assert_eq!(parse_line("8 lgc 2472"), Some((8, HelperCmd::ClockCap(2472))));
|
||||
assert_eq!(parse_line("9 rgc"), Some((9, HelperCmd::ClockReset)));
|
||||
assert_eq!(parse_line("10 lmc 14001"), Some((10, HelperCmd::MemClock(14001))));
|
||||
assert_eq!(parse_line("11 rmc"), Some((11, HelperCmd::MemReset)));
|
||||
assert_eq!(smi_args("0", &HelperCmd::MemClock(14001)).unwrap(), vec!["-i", "0", "-lmc", "14001,14001"]);
|
||||
assert_eq!(smi_args("0", &HelperCmd::MemReset).unwrap(), vec!["-i", "0", "-rmc"]);
|
||||
assert_eq!(parse_line("3 dev 1"), Some((3, HelperCmd::Dev("1".into()))));
|
||||
assert_eq!(parse_line("5 403"), Some((5, HelperCmd::PowerLimit(403))), "the 0.3.9 form");
|
||||
assert_eq!(parse_line("quit"), Some((0, HelperCmd::Quit)));
|
||||
assert_eq!(parse_line("remove"), Some((0, HelperCmd::Remove)));
|
||||
assert_eq!(parse_line("12 reregister"), Some((12, HelperCmd::Reregister)));
|
||||
assert_eq!(parse_line("12 reregister C:\\evil.exe"), None, "no path argument: the helper picks the install folder itself");
|
||||
assert_eq!(smi_args("0", &HelperCmd::Reregister), None);
|
||||
assert!(readback_command().contains("Actions[0].Execute"));
|
||||
// the helper's folder never follows IGNEUM_APP_DATA (the task has no environment of ours)
|
||||
assert!(helper_dir().ends_with(Path::new("app").join("sweep")));
|
||||
assert!(helper_dir().starts_with(crate::platform::fixed_data_root()));
|
||||
// nothing else: no shell, no path, no string argument, no oversized number
|
||||
for bad in ["7 pl 460; calc", "7 pl -460", "7 pl 4.60", "7 lgc 0,2472", "7 rm C:\\x", "x pl 460", "7 pl", "7 lgc 12345678", "7 dev ../1", "", "7 pl 460 extra"] {
|
||||
assert_eq!(parse_line(bad), None, "{bad:?}");
|
||||
|
|
@ -253,6 +336,24 @@ mod tests {
|
|||
assert!(query_command().contains("Get-ScheduledTask -TaskName 'Igneum Power Helper'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_kit_run_from_a_jobs_folder_registers_the_installed_exe() {
|
||||
let kit = Path::new(r"C:\Users\Admin\AppData\Local\igneum\app\jobs\ember-kit-5\igneum-app-ember.exe");
|
||||
let own = Path::new(r"C:\Users\Admin\AppData\Local\Programs\Igneum Miner\igneum-app.exe");
|
||||
// no installed candidate exists on this machine: the running exe stands
|
||||
assert_eq!(task_exe(kit, &[PathBuf::from(r"C:\nonexistent\igneum-app.exe")]), kit.to_path_buf());
|
||||
assert_eq!(task_exe(own, &[]), own.to_path_buf());
|
||||
// an installed candidate that exists (the test binary stands in for it) wins for a kit, not for the installed exe
|
||||
let here = std::env::current_exe().unwrap();
|
||||
assert!(here.is_file());
|
||||
assert_eq!(task_exe(kit, &[here.clone()]), here);
|
||||
// a measurement run's scratch copy (run 6, 6 October 2026) is not under jobs\ and still yields
|
||||
let scratch = Path::new(r"C:\Users\Admin\AppData\Local\igneum-tune-20261006-170105\bin\igneum-app.exe");
|
||||
assert_eq!(task_exe(scratch, &[here.clone()]), here);
|
||||
// the installed exe itself stands (matched by path, any case or separator)
|
||||
assert_eq!(task_exe(own, &[PathBuf::from(r"c:/users/admin/appdata/local/programs/igneum miner/IGNEUM-APP.EXE"), here]), own.to_path_buf());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stale_command_file_does_not_run_at_start() {
|
||||
// the helper's start reads the highest sequence already in the file and runs nothing below or at it
|
||||
|
|
|
|||
|
|
@ -132,6 +132,25 @@ struct Tools {
|
|||
cuda: bool,
|
||||
}
|
||||
|
||||
/// The node's re-derivation boundary (0.3.14, 6 October 2026): the chain block its EVM restarted at after the
|
||||
/// bodies below the pruning point were gone (`igneum_getExecStatus.restartNumber`; on a 0.3.13 node the
|
||||
/// `startedFrom` text "restart at chain block N"), else 0. The prover never claims work below it (no bodies, no
|
||||
/// state: unprovable on every node).
|
||||
fn exec_boundary(shared: &Shared) -> u64 {
|
||||
let st = match evm_rpc(shared, "igneum_getExecStatus", json!([]), Duration::from_secs(5)) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
if let Some(n) = st["restartNumber"].as_u64() {
|
||||
return n;
|
||||
}
|
||||
if let Some(n) = st["restartNumber"].as_str().and_then(|x| u64::from_str_radix(x.trim_start_matches("0x"), 16).ok()) {
|
||||
return n;
|
||||
}
|
||||
let text = st["startedFrom"].as_str().unwrap_or("");
|
||||
text.strip_prefix("restart at chain block ").and_then(|t| t.split_whitespace().next()).and_then(|t| t.parse().ok()).unwrap_or(0)
|
||||
}
|
||||
|
||||
fn evm_rpc(shared: &Shared, method: &str, params: Value, timeout: Duration) -> Result<Value, String> {
|
||||
let body = json!({ "jsonrpc": "2.0", "id": 1, "method": method, "params": params }).to_string();
|
||||
let tmp = std::env::temp_dir().join(format!("igneum-prover-{}-{}.json", std::process::id(), method));
|
||||
|
|
@ -602,6 +621,9 @@ fn loop_forever(shared: Arc<Shared>, bin_dir: PathBuf) {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
// shards below the re-derivation boundary are never attempted (no bodies, no state on any node)
|
||||
let boundary = exec_boundary(&shared);
|
||||
let work: Vec<Work> = work.into_iter().filter(|w| w.number >= boundary).collect();
|
||||
let Some(w) = choose(&work, &attempted) else {
|
||||
set(&shared, |p| {
|
||||
p.status = if submitted.is_empty() { "idle".into() } else { "submitted".into() };
|
||||
|
|
@ -634,7 +656,11 @@ fn loop_forever(shared: Arc<Shared>, bin_dir: PathBuf) {
|
|||
let fixture = dir.join(format!("block-{}.json", w.number));
|
||||
let results = dir.join(format!("results-{}-{}.json", w.number, w.shard));
|
||||
let outcome: Result<(), String> = (|| {
|
||||
let export = evm_rpc(&shared, "igneum_exportSegments", json!(["0x0", format!("{:#x}", w.number)]), Duration::from_secs(120))?;
|
||||
// the export from one block below (0.3.14): the node's account dump after block n-1 seeds the exporter;
|
||||
// never from 0 (on a restarted node the records below the restart carry zero roots and the exporter
|
||||
// refused every cut, the fleet 16:02Z)
|
||||
let from = w.number.saturating_sub(1);
|
||||
let export = evm_rpc(&shared, "igneum_exportSegments", json!([format!("{from:#x}"), format!("{:#x}", w.number)]), Duration::from_secs(120))?;
|
||||
std::fs::write(&seq, export.to_string()).map_err(|e| e.to_string())?;
|
||||
let (seq_p, fix_p, res_p) = if t.wsl { (wsl_path(&seq), wsl_path(&fixture), wsl_path(&results)) } else { (seq.display().to_string(), fixture.display().to_string(), results.display().to_string()) };
|
||||
let (ok, out) = run_tool(&shared, t, &t.export, &[seq_p, w.number.to_string(), fix_p.clone()], &[], Duration::from_secs(600), &dir.join(format!("export-{}.log", w.number)));
|
||||
|
|
@ -750,6 +776,9 @@ fn pick_segment(shared: &Shared, work: &[Work], key_hash: &str, attempted: &mut
|
|||
return None;
|
||||
}
|
||||
let (start, n, unproven, tip_daa) = (hexu(&v1["start"]), hexu(&v1["segmentBlocks"]).max(1), hexu(&v1["unprovenDaa"]), hexu(&st["tipDaa"]));
|
||||
// segments below the re-derivation boundary are never claimed: no bodies, no state, unprovable anywhere
|
||||
let boundary = exec_boundary(shared);
|
||||
let start = if boundary > start { boundary.div_ceil(n) * n } else { start };
|
||||
let segs = crate::segments::whole_segments(start, n, unproven, work);
|
||||
let need = crate::segments::need_daa(last_secs);
|
||||
let cands = crate::segments::candidates(&segs, tip_daa, need, key_hash, attempted);
|
||||
|
|
@ -815,7 +844,10 @@ fn prove_segment(shared: &Shared, t: &Tools, seg: &crate::segments::SegmentWork,
|
|||
shared.log(&format!("prover: segment {first}..{last} claimed ({} shards{}): export, cut, chain ({}), sign, submit", seg.shards.len(), if prev_file.is_some() { ", continuing the previous segment's proof" } else { ", fresh" }, if t.cuda { "CUDA" } else { "CPU" }));
|
||||
// 1. export once, cut every block
|
||||
let seq = dir.join("seq.json");
|
||||
let export = evm_rpc(shared, "igneum_exportSegments", json!(["0x0", format!("{last:#x}")]), Duration::from_secs(300))?;
|
||||
// the export from one block below the segment (0.3.14): the node's account dump after block first-1 seeds the
|
||||
// exporter; nothing older is read
|
||||
let from = first.saturating_sub(1);
|
||||
let export = evm_rpc(shared, "igneum_exportSegments", json!([format!("{from:#x}"), format!("{last:#x}")]), Duration::from_secs(300))?;
|
||||
std::fs::write(&seq, export.to_string()).map_err(|e| e.to_string())?;
|
||||
let mut fixtures: Vec<String> = Vec::new();
|
||||
for b in first..=last {
|
||||
|
|
|
|||
|
|
@ -251,6 +251,14 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
|
|||
shared.send(Cmd::ApplyCards(choices));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
"/api/tune/goal" => {
|
||||
// Ember 2: the goal, the electricity price (pence per kWh) and the hill-climb switch
|
||||
let goal = body.get("goal").and_then(|v| v.as_str()).map(|g| crate::ember::Goal::parse(g).name().to_string());
|
||||
let price = body.get("price_pence").and_then(|v| v.as_f64()).filter(|p| (0.0..=500.0).contains(p));
|
||||
let climb = body.get("climb").and_then(|v| v.as_bool());
|
||||
shared.send(Cmd::TuneGoal(goal, price, climb));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
"/api/settings" => {
|
||||
let identities = body.get("identities").and_then(|v| v.as_u64()).map(|v| v.clamp(1, 64) as u32);
|
||||
let vote = body.get("vote").and_then(|v| v.as_bool());
|
||||
|
|
@ -325,6 +333,11 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
|
|||
shared.send(Cmd::PowerControl(on));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
// a measurement engine's live tune state for one card (forwarded by its job playbook): the row shows the step
|
||||
"/api/tune-progress" => {
|
||||
shared.send(Cmd::TuneProgress(body));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
"/api/sweep/enable" => {
|
||||
let on = body.get("on").and_then(|v| v.as_bool()).ok_or("on missing")?;
|
||||
shared.send(Cmd::SweepEnable(on));
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ pub struct CardState {
|
|||
pub clock_min_mhz: u32, // the vendor's floor for a cap (0 = 60% of the maximum)
|
||||
pub gclk_mhz: f64, // core clock now
|
||||
pub clock_cap_mhz: u32, // the cap in force (0 = unlocked)
|
||||
pub mem_cap_mhz: u32, // Ember 2: the memory clock set by the tune (0 = the driver's default)
|
||||
pub amd_ordinal: i64, // the `amd N` ordinal of igneum-gpu-telemetry (-1 = unknown)
|
||||
pub driver: String, // the driver version (nvidia-smi, or the worker's race line)
|
||||
pub program_class: String, // the program class of the race line (loads and wide loads per hash); "" = unknown
|
||||
|
|
@ -125,6 +126,15 @@ pub struct CardState {
|
|||
pub tune_clock_mhz: u32, // the clock cap the last tune chose (0 = unlocked)
|
||||
pub tune_source: String, // full | confirm | baseline
|
||||
pub tune_line: String, // "Tuned: 122.3 MH/s at 290 W (0.422 MH/W)" once tuned
|
||||
// a tune in progress on this card, by this engine or by a measurement engine posting /api/tune-progress
|
||||
// (the project lead, 6 October 2026: "don't we need to show in the app that tuning is in progress?")
|
||||
pub tune_step: u32,
|
||||
pub tune_steps: u32,
|
||||
pub tune_eta_s: i64,
|
||||
pub tune_plan: String,
|
||||
// Ember 2: the memory clock the last tune chose and the measured curve (every row of the last plan)
|
||||
pub tune_mem_mhz: u32,
|
||||
pub tune_curve: Vec<serde_json::Value>,
|
||||
// the kernel variant race (docs/design/miner-tuning.md): what the worker's last race chose
|
||||
pub variant: String,
|
||||
pub race_mhs: f64,
|
||||
|
|
@ -283,6 +293,13 @@ pub struct SettingsState {
|
|||
/// Ember Tune is paused fleet-wide by the signed manifest's kill switch (tuning.ember.enabled = false)
|
||||
pub tuning_off: bool,
|
||||
pub tuning_note: String,
|
||||
/// Ember 2: the goal (efficiency | balanced | rate), the electricity price in pence per kWh, the hill-climb switch
|
||||
pub tune_goal: String,
|
||||
pub power_price_pence: f64,
|
||||
pub tune_climb: bool,
|
||||
/// the manifest's tune period in seconds (tuning.ember.period_s, default 7 days): a card is due again at
|
||||
/// sweep_at + tune_period_s, or at once after a driver major or program-class change
|
||||
pub tune_period_s: u64,
|
||||
/// the miner software's dev fee switch (settings; `--dev-fee 0` when off)
|
||||
pub dev_fee: bool,
|
||||
/// devnet only: the node trusts proof records without a verifier (`IGNEUM_PROOF_VERIFY=trust`)
|
||||
|
|
|
|||
|
|
@ -375,6 +375,12 @@ while ($true) {
|
|||
} elseif ($op -eq 'rgc') {
|
||||
$out = (& $Smi -i $Device -rgc 2>&1 | Out-String).Trim()
|
||||
"$(Get-Date -Format o) $($p[0]) -rgc : $out" | Out-File -FilePath $log -Append -Encoding utf8
|
||||
} elseif ($op -eq 'lmc' -and $v -match '^\d+$') {
|
||||
$out = (& $Smi -i $Device -lmc "$v,$v" 2>&1 | Out-String).Trim()
|
||||
"$(Get-Date -Format o) $($p[0]) -lmc $v,$v : $out" | Out-File -FilePath $log -Append -Encoding utf8
|
||||
} elseif ($op -eq 'rmc') {
|
||||
$out = (& $Smi -i $Device -rmc 2>&1 | Out-String).Trim()
|
||||
"$(Get-Date -Format o) $($p[0]) -rmc : $out" | Out-File -FilePath $log -Append -Encoding utf8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -411,6 +417,8 @@ while true; do
|
|||
pl) case "$v" in ''|*[!0-9]*) ;; *) echo "$(date -u +%FT%TZ) $1 -pl $v : $("$smi" -i "$dev" -pl "$v" 2>&1)" >> "$dir/helper.log";; esac ;;
|
||||
lgc) case "$v" in ''|*[!0-9]*) ;; *) echo "$(date -u +%FT%TZ) $1 -lgc 0,$v : $("$smi" -i "$dev" -lgc "0,$v" 2>&1)" >> "$dir/helper.log";; esac ;;
|
||||
rgc) echo "$(date -u +%FT%TZ) $1 -rgc : $("$smi" -i "$dev" -rgc 2>&1)" >> "$dir/helper.log" ;;
|
||||
lmc) case "$v" in ''|*[!0-9]*) ;; *) echo "$(date -u +%FT%TZ) $1 -lmc $v,$v : $("$smi" -i "$dev" -lmc "$v,$v" 2>&1)" >> "$dir/helper.log";; esac ;;
|
||||
rmc) echo "$(date -u +%FT%TZ) $1 -rmc : $("$smi" -i "$dev" -rmc 2>&1)" >> "$dir/helper.log" ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
|
@ -614,6 +622,7 @@ mod tests {
|
|||
for s in [helper_script_windows(), helper_script_unix()] {
|
||||
assert!(s.contains("cmd.txt") && s.contains("quit") && s.contains("-pl") && s.contains("20 min"));
|
||||
assert!(s.contains("-lgc") && s.contains("-rgc"), "the clock cap and its reset");
|
||||
assert!(s.contains("-lmc") && s.contains("-rmc"), "Ember 2: the memory clock and its reset");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/* Igneum Miner dashboard (miner-ui-2). The site's tokens (site/index.html): obsidian, graphite, ember, molten, bone,
|
||||
ash; Unbounded for headings, IBM Plex Sans for text, IBM Plex Mono for numbers and labels. Fonts ship in the binary.
|
||||
One type scale (--t-*), one spacing scale (--s-*), one colour set (:root), one accent (ember), used by every page.
|
||||
Layout: a rail on the left (six sections), a thin top bar, the status strip under it, the page in the middle, the
|
||||
log drawer at the bottom. The window lays out from 900 x 600 up (the hosts say the same minimum).
|
||||
/* Igneum Miner dashboard (miner-ui-3). The site's tokens (site/index.html): obsidian, graphite, ember, molten, bone,
|
||||
ash; Unbounded for the page title, the big button and the hero numbers; IBM Plex Sans for words; IBM Plex Mono for
|
||||
labels, units and small numbers. Fonts ship in the binary. One type scale (--t-*), one spacing scale (--s-*), one
|
||||
accent (ember). Dark by default, light under prefers-color-scheme or [data-theme=light]. Layout: a rail on the left
|
||||
(four sections), a thin top bar, the status strip under it, the page in the middle, the log drawer at the bottom.
|
||||
The window lays out from 900 x 600 (the hosts' minimum); under 720 px the rail becomes a bottom tab bar.
|
||||
Nothing here is newer than 2022 CSS (the WebView2 host). */
|
||||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url(fonts/IBMPlexMono-400.woff2) format('woff2')}
|
||||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url(fonts/IBMPlexMono-500.woff2) format('woff2')}
|
||||
|
|
@ -14,36 +15,42 @@
|
|||
@font-face{font-family:'Unbounded';font-style:normal;font-weight:900;font-display:swap;src:url(fonts/Unbounded-900.woff2) format('woff2')}
|
||||
|
||||
:root{
|
||||
/* colour */
|
||||
--obsidian:#0C0C0E;--graphite:#16161A;--line:#2A2A30;--line-2:#3A3A42;--ember:#F2541B;--ember-hi:#FF6A2B;--molten:#FFB35C;--bone:#F4F1EC;--ash:#9A9A9E;--ink-2:#C9C7C2;--ember-ink:#0C0C0E;
|
||||
--ember-12:rgba(242,84,27,.12);--ember-40:rgba(242,84,27,.4);--molten-10:rgba(255,179,92,.1);--molten-40:rgba(255,179,92,.4);--node-blue:#7FA7C9;--nvidia:#8BE37A;--rail-bg:#111114;
|
||||
/* colour, dark */
|
||||
--obsidian:#0C0C0E;--graphite:#16161A;--row:#111114;--line:#2A2A30;--line-2:#3A3A42;--ember:#F2541B;--ember-hi:#FF6A2B;--molten:#FFB35C;--bone:#F4F1EC;--ash:#9A9A9E;--ink-2:#C9C7C2;--ember-ink:#0C0C0E;
|
||||
--ember-12:rgba(242,84,27,.12);--ember-40:rgba(242,84,27,.4);--molten-10:rgba(255,179,92,.1);--molten-40:rgba(255,179,92,.4);--nvidia:#8BE37A;--rail-bg:#111114;--hover:rgba(255,255,255,.04);--top-bg:rgba(12,12,14,.86);--shadow:rgba(0,0,0,.6);--scrim:rgba(12,12,14,.72);--ok:#FFB35C;
|
||||
/* type scale */
|
||||
--t-xs:11px;--t-sm:12px;--t-base:13px;--t-md:14px;--t-lg:15px;--t-xl:16px;--t-2xl:18px;--t-num:26px;--t-h3:16px;--t-h2:32px;--t-h1:52px;
|
||||
--t-xs:11px;--t-sm:12px;--t-base:13px;--t-md:14px;--t-lg:15px;--t-xl:17px;--t-num:24px;--t-hero:34px;--t-h2:28px;--t-h1:44px;
|
||||
/* spacing scale */
|
||||
--s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:20px;--s-6:28px;--s-7:40px;
|
||||
--gutter:var(--s-6);--card-pad:22px;--card-r:18px;--tile-pad:18px 20px;--tile-r:14px;--gap:var(--s-5);--gap-tile:var(--s-3);
|
||||
--s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:24px;--s-6:32px;
|
||||
--gutter:var(--s-6);--card-pad:var(--s-5);--card-r:16px;--row-r:12px;--gap:var(--s-4);
|
||||
--sans:'IBM Plex Sans',system-ui,-apple-system,sans-serif;--mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--head:'Unbounded',sans-serif;
|
||||
--top:60px;--bottom:0px;--drawer-h:260px;--rail:196px}
|
||||
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){
|
||||
--obsidian:#F4F1EC;--graphite:#FFFFFF;--row:#FAF8F5;--line:#E2DED8;--line-2:#CFCAC2;--ember:#E04A14;--ember-hi:#F2541B;--molten:#B8731F;--bone:#16161A;--ash:#6B6B70;--ink-2:#3C3C42;--ember-ink:#FFFFFF;
|
||||
--ember-12:rgba(224,74,20,.1);--ember-40:rgba(224,74,20,.4);--molten-10:rgba(184,115,31,.1);--molten-40:rgba(184,115,31,.4);--nvidia:#3C9B2C;--rail-bg:#EFEBE4;--hover:rgba(0,0,0,.04);--top-bg:rgba(244,241,236,.88);--shadow:rgba(0,0,0,.18);--scrim:rgba(244,241,236,.72);--ok:#B8731F}}
|
||||
:root[data-theme="light"]{
|
||||
--obsidian:#F4F1EC;--graphite:#FFFFFF;--row:#FAF8F5;--line:#E2DED8;--line-2:#CFCAC2;--ember:#E04A14;--ember-hi:#F2541B;--molten:#B8731F;--bone:#16161A;--ash:#6B6B70;--ink-2:#3C3C42;--ember-ink:#FFFFFF;
|
||||
--ember-12:rgba(224,74,20,.1);--ember-40:rgba(224,74,20,.4);--molten-10:rgba(184,115,31,.1);--molten-40:rgba(184,115,31,.4);--nvidia:#3C9B2C;--rail-bg:#EFEBE4;--hover:rgba(0,0,0,.04);--top-bg:rgba(244,241,236,.88);--shadow:rgba(0,0,0,.18);--scrim:rgba(244,241,236,.72);--ok:#B8731F}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;background:var(--obsidian);color:var(--bone);font-family:var(--sans);font-size:var(--t-lg);line-height:1.5;-webkit-font-smoothing:antialiased;overflow:hidden;user-select:none;-webkit-user-select:none;font-variant-numeric:tabular-nums}
|
||||
body{margin:0;background:var(--obsidian);color:var(--bone);font-family:var(--sans);font-size:var(--t-md);line-height:1.5;-webkit-font-smoothing:antialiased;overflow:hidden;user-select:none;-webkit-user-select:none;font-variant-numeric:tabular-nums}
|
||||
.mono,code,pre{font-family:var(--mono);font-variant-numeric:tabular-nums}
|
||||
.dim{color:var(--ash)}
|
||||
h1,h2,h3{font-family:var(--head);margin:0;line-height:1.1;text-wrap:balance}
|
||||
h1{font-weight:900;font-size:var(--t-h1);letter-spacing:-.01em}
|
||||
h2{font-weight:700;font-size:var(--t-h2)}
|
||||
h3{font-weight:700;font-size:var(--t-h3)}
|
||||
h3{font-weight:700;font-size:var(--t-xl)}
|
||||
p{margin:0}
|
||||
a{color:inherit}
|
||||
button{font:inherit;color:inherit}
|
||||
input,textarea{font-variant-numeric:tabular-nums}
|
||||
.eyebrow{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.18em;text-transform:uppercase;color:var(--ash);display:inline-flex;align-items:center;gap:var(--s-2);white-space:nowrap}
|
||||
.eyebrow{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.16em;text-transform:uppercase;color:var(--ash);display:inline-flex;align-items:center;gap:var(--s-2);white-space:nowrap}
|
||||
.eyebrow.ember{color:var(--ember)}
|
||||
[hidden]{display:none !important}
|
||||
::selection{background:rgba(242,84,27,.45)}
|
||||
|
||||
/* buttons */
|
||||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);min-height:44px;padding:10px 20px;border-radius:10px;font-weight:600;font-size:var(--t-lg);border:1px solid var(--line-2);color:var(--bone);background:transparent;cursor:pointer;transition:transform .15s ease,background .15s ease,border-color .15s ease,opacity .15s ease,color .15s ease;white-space:nowrap}
|
||||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);min-height:40px;padding:8px 18px;border-radius:10px;font-weight:600;font-size:var(--t-md);border:1px solid var(--line-2);color:var(--bone);background:transparent;cursor:pointer;transition:transform .15s ease,background .15s ease,border-color .15s ease,opacity .15s ease,color .15s ease;white-space:nowrap}
|
||||
.btn:hover{transform:translateY(-1px);border-color:var(--ash)}
|
||||
.btn:active{transform:none}
|
||||
.btn:disabled{opacity:.4;cursor:default;transform:none}
|
||||
|
|
@ -68,9 +75,9 @@ body.mac .rail-brand{padding-top:30px}
|
|||
.rail-nav{display:flex;flex-direction:column;gap:3px}
|
||||
.nav{position:relative;display:flex;align-items:center;gap:12px;width:100%;min-height:42px;padding:8px 12px;border:1px solid transparent;border-radius:11px;background:transparent;color:var(--ink-2);font-weight:500;font-size:var(--t-md);text-align:left;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}
|
||||
.nav svg{width:19px;height:19px;flex:0 0 19px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round;color:var(--ash);transition:color .15s ease}
|
||||
.nav:hover{background:rgba(255,255,255,.04);color:var(--bone)}
|
||||
.nav:hover{background:var(--hover);color:var(--bone)}
|
||||
.nav:hover svg{color:var(--ink-2)}
|
||||
.nav.on{background:var(--ember-12);border-color:rgba(242,84,27,.28);color:var(--bone);font-weight:600}
|
||||
.nav.on{background:var(--ember-12);border-color:var(--ember-40);color:var(--bone);font-weight:600}
|
||||
.nav.on svg{color:var(--ember)}
|
||||
.nav.on::before{content:"";position:absolute;left:-13px;top:10px;bottom:10px;width:3px;border-radius:0 3px 3px 0;background:var(--ember)}
|
||||
.nav.small{min-height:36px;font-size:var(--t-base);color:var(--ash)}
|
||||
|
|
@ -85,7 +92,7 @@ body.mac .rail-brand{padding-top:30px}
|
|||
.rail-version{font-size:var(--t-xs);color:var(--ash);padding:8px 12px 0;letter-spacing:.06em}
|
||||
|
||||
/* top bar */
|
||||
.top{position:fixed;top:0;left:0;right:0;height:var(--top);display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);padding:0 var(--gutter);background:rgba(12,12,14,.86);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid rgba(42,42,48,.7);z-index:20;-webkit-app-region:drag}
|
||||
.top{position:fixed;top:0;left:0;right:0;height:var(--top);display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);padding:0 var(--gutter);background:var(--top-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--line);z-index:20;-webkit-app-region:drag}
|
||||
.top button,.top .pill{-webkit-app-region:no-drag}
|
||||
body.mac:not(.has-rail) .top{padding-left:92px}
|
||||
body.has-rail .top{left:var(--rail)}
|
||||
|
|
@ -96,21 +103,17 @@ body.has-rail .top{left:var(--rail)}
|
|||
.page-title h1{font-size:19px;font-weight:700;letter-spacing:0;white-space:nowrap}
|
||||
.page-sub{font-size:var(--t-base);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
|
||||
.top-right{display:flex;align-items:center;gap:var(--s-3);flex:0 0 auto;min-width:0}
|
||||
.top-status{font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:380px}
|
||||
.top-status .pc+.pc::before{content:"·";margin:0 7px;color:var(--line-2)}
|
||||
.pill{display:inline-flex;align-items:center;gap:var(--s-2);font-family:var(--mono);font-size:var(--t-sm);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);border:1px solid var(--line);border-radius:999px;padding:6px 12px 6px 10px;background:var(--graphite);white-space:nowrap;font-variant-numeric:tabular-nums;min-width:112px;justify-content:center}
|
||||
.pill.on{color:var(--molten);border-color:var(--molten-40)}
|
||||
.pill.warn{color:var(--ember)}
|
||||
.dot{width:8px;height:8px;border-radius:50%;background:var(--ash);display:inline-block;flex:0 0 8px}
|
||||
.dot.small{width:7px;height:7px;flex-basis:7px}
|
||||
.on .dot,.dot.live{background:var(--molten);animation:pulse 2s ease-in-out infinite}
|
||||
.warn .dot{background:var(--ember);animation:none}
|
||||
.warn .dot,.dot.bad{background:var(--ember);animation:none}
|
||||
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(255,179,92,.5)}50%{box-shadow:0 0 0 7px rgba(255,179,92,0)}}
|
||||
@media (prefers-reduced-motion:reduce){.on .dot,.dot.live{animation:none}}
|
||||
|
||||
/* the status strip under the top bar (app.js, Notices): one notice at a time. It takes no room while empty; main's
|
||||
top moves once per change with a 150 ms transition (layoutStrip), never per poll. Tones: default molten (running,
|
||||
available, done), bad ember (failed, clock block, urgent). */
|
||||
top moves once per change with a 150 ms transition (layoutStrip), never per poll. */
|
||||
.notices{position:fixed;top:var(--top);left:0;right:0;z-index:19}
|
||||
body.has-rail .notices{left:var(--rail)}
|
||||
.notice{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2) var(--s-4);padding:8px calc(var(--gutter) - 6px) 8px var(--gutter);background:var(--molten-10);border-bottom:1px solid var(--molten-40);font-size:var(--t-base);line-height:1.4;color:var(--bone)}
|
||||
|
|
@ -124,11 +127,18 @@ body.has-rail .notices{left:var(--rail)}
|
|||
.notice-close:hover{color:var(--bone);border-color:var(--line-2)}
|
||||
.notice.update-urgent .notice-close{color:#fff}
|
||||
.notice-detail{flex-basis:100%;font-size:var(--t-sm);color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:-3px}
|
||||
.notice .prog{flex-basis:100%;height:3px;background:rgba(255,255,255,.12);border-radius:2px;overflow:hidden;margin-top:-3px}
|
||||
.notice .prog{flex-basis:100%;height:3px;background:rgba(127,127,127,.2);border-radius:2px;overflow:hidden;margin-top:-3px}
|
||||
.notice .prog i{display:block;height:100%;width:0;background:var(--ember);transition:width .5s linear}
|
||||
|
||||
/* a question in place of a dialog: the quit strip over the page, the inline asks inside a card */
|
||||
.ask-wrap{position:fixed;left:0;right:0;bottom:0;z-index:36;display:flex;justify-content:center;padding:0 var(--s-4) var(--s-4);pointer-events:none}
|
||||
body.has-rail .ask-wrap{left:var(--rail)}
|
||||
.ask{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;background:var(--graphite);border:1px solid var(--ember-40);border-radius:14px;padding:12px 16px;box-shadow:0 14px 40px var(--shadow);pointer-events:auto;max-width:720px;animation:rise .2s ease}
|
||||
.ask-text{flex:1 1 260px;font-size:var(--t-md);min-width:0}
|
||||
.ask.inline{box-shadow:none;background:var(--ember-12);margin-top:var(--s-3);animation:none}
|
||||
|
||||
/* screens and pages */
|
||||
main{position:absolute;top:var(--top);bottom:0;left:0;right:0;overflow:auto;padding:0 var(--gutter);overscroll-behavior:contain;transition:top .15s ease}
|
||||
main{position:absolute;top:var(--top);bottom:var(--bottom);left:0;right:0;overflow:auto;padding:0 var(--gutter);overscroll-behavior:contain;transition:top .15s ease}
|
||||
@media (prefers-reduced-motion:reduce){main{transition:none}}
|
||||
body.has-rail main{left:var(--rail)}
|
||||
body.drawer-open main{bottom:var(--drawer-h)}
|
||||
|
|
@ -140,15 +150,14 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.page{display:flex;flex-direction:column;gap:var(--gap);animation:rise .3s ease}
|
||||
|
||||
/* welcome */
|
||||
.hero{min-height:calc(100vh - var(--top));display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:var(--s-4);padding:var(--s-7) 0 48px}
|
||||
.coin-wrap{position:relative;width:148px;height:148px;margin-bottom:6px}
|
||||
.coin-wrap::before{content:"";position:absolute;inset:-40px;border-radius:50%;background:radial-gradient(circle,rgba(242,84,27,.28) 0,rgba(242,84,27,0) 65%);animation:breathe 4s ease-in-out infinite}
|
||||
.coin{position:relative;display:block;border-radius:50%;filter:drop-shadow(0 10px 30px rgba(242,84,27,.35))}
|
||||
.hero{min-height:calc(100vh - var(--top));display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:var(--s-4);padding:var(--s-6) 0 48px}
|
||||
.mark-wrap{position:relative;width:120px;height:120px;border-radius:26px;background:#0C0C0E;display:flex;align-items:center;justify-content:center;margin-bottom:6px;box-shadow:0 20px 50px rgba(242,84,27,.25)}
|
||||
.mark-wrap::before{content:"";position:absolute;inset:-40px;border-radius:50%;background:radial-gradient(circle,rgba(242,84,27,.28) 0,rgba(242,84,27,0) 65%);animation:breathe 4s ease-in-out infinite;z-index:-1}
|
||||
@keyframes breathe{0%,100%{opacity:.7;transform:scale(1)}50%{opacity:1;transform:scale(1.08)}}
|
||||
@media (prefers-reduced-motion:reduce){.coin-wrap::before{animation:none}}
|
||||
.lead{font-size:var(--t-2xl);color:var(--ink-2);max-width:54ch}
|
||||
.three{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gap-tile);width:100%;max-width:860px;margin-top:10px;text-align:left}
|
||||
.tile{background:var(--graphite);border:1px solid var(--line);border-radius:var(--tile-r);padding:var(--tile-pad);display:flex;flex-direction:column;gap:6px;min-width:0}
|
||||
@media (prefers-reduced-motion:reduce){.mark-wrap::before{animation:none}}
|
||||
.lead{font-size:var(--t-xl);color:var(--ink-2);max-width:54ch}
|
||||
.three{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s-3);width:100%;max-width:860px;margin-top:10px;text-align:left}
|
||||
.tile{background:var(--graphite);border:1px solid var(--line);border-radius:14px;padding:18px 20px;display:flex;flex-direction:column;gap:6px;min-width:0}
|
||||
.tile .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.14em;color:var(--ember)}
|
||||
.tile .t{font-family:var(--head);font-weight:700;font-size:var(--t-lg);line-height:1.25}
|
||||
.tile .s{font-size:var(--t-base);color:var(--ash);line-height:1.45}
|
||||
|
|
@ -162,6 +171,9 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.note{font-size:var(--t-base);color:var(--ash);line-height:1.5}
|
||||
.note.small{font-size:var(--t-sm);word-break:break-all}
|
||||
.help{font-size:var(--t-base);color:var(--ash);line-height:1.5;max-width:64ch}
|
||||
.line-text{font-size:var(--t-md);color:var(--ink-2);line-height:1.5}
|
||||
.line-text.ok{color:var(--molten)}
|
||||
.line-text.bad{color:var(--ember)}
|
||||
.cards{display:flex;flex-direction:column;gap:var(--s-3);margin-top:var(--s-2)}
|
||||
.card{background:var(--graphite);border:1px solid var(--line);border-radius:var(--card-r);padding:var(--card-pad);min-width:0}
|
||||
.card.detecting{display:flex;align-items:center;gap:18px}
|
||||
|
|
@ -169,11 +181,13 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.card.detecting .s{font-size:var(--t-sm);color:var(--ash);margin-top:var(--s-1)}
|
||||
.spinner{width:28px;height:28px;border-radius:50%;border:3px solid var(--line-2);border-top-color:var(--ember);animation:spin 1s linear infinite;flex:0 0 28px}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.top-gap{margin-top:var(--s-4)}
|
||||
.indent{margin-left:52px}
|
||||
|
||||
/* GPU rows (first run) */
|
||||
.gpu-row{display:flex;align-items:center;gap:var(--s-4);background:var(--graphite);border:1px solid var(--line);border-radius:var(--card-r);padding:16px 20px;min-width:0;flex-wrap:wrap}
|
||||
.gpu-row.off{opacity:.72}
|
||||
.badge{width:48px;height:48px;border-radius:13px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;flex:0 0 48px;border:1px solid var(--line-2);color:var(--molten);background:var(--obsidian)}
|
||||
.badge{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;flex:0 0 44px;border:1px solid var(--line-2);color:var(--molten);background:var(--obsidian)}
|
||||
.badge.apple{color:var(--bone)}
|
||||
.badge.nvidia{color:var(--nvidia)}
|
||||
.badge.amd{color:var(--ember)}
|
||||
|
|
@ -183,17 +197,15 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.kind.discrete,.kind.apple{color:var(--molten);border-color:var(--molten-40)}
|
||||
.kind.external{color:var(--bone)}
|
||||
.gpu-row .meta{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);display:flex;flex-wrap:wrap;gap:4px 14px}
|
||||
.gpu-row .meta b{color:var(--ink-2);font-weight:500}
|
||||
.gpu-row .reason{font-size:var(--t-sm);color:var(--ash)}
|
||||
.gpu-row .msg{font-size:var(--t-sm);color:var(--ember)}
|
||||
.gpu-row .switch{padding:0;flex:0 0 auto;margin-left:auto}
|
||||
/* hot-plug (src/hotplug.rs): a removed card dims, a faulty one is named in ember, neither has live controls */
|
||||
.gpu-row.removed,.gpu-line.removed,.set-card.removed{opacity:.5}
|
||||
.gpu-row.unusable .name,.gpu-line.unusable .name,.set-card.unusable .name{color:var(--ember)}
|
||||
.gpu-row.removed,.gpu-line.removed{opacity:.5}
|
||||
.gpu-row.unusable .name,.gpu-line.unusable .name{color:var(--ember)}
|
||||
|
||||
/* the Mine page: the big switch and the three numbers */
|
||||
.hero-row{display:grid;grid-template-columns:1.3fr 1fr 1fr 1fr;gap:var(--gap-tile)}
|
||||
.toggle-big{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:4px;min-height:112px;padding:18px 20px;border-radius:var(--tile-r);border:1px solid var(--ember);background:var(--ember);color:var(--ember-ink);cursor:pointer;text-align:left;transition:background .15s ease,border-color .15s ease,transform .15s ease,color .15s ease;min-width:0}
|
||||
/* the Mine page: the big button and the live total */
|
||||
.hero-row{display:grid;grid-template-columns:minmax(220px,1fr) 2fr;gap:var(--s-3)}
|
||||
.toggle-big{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:4px;min-height:112px;padding:18px 20px;border-radius:14px;border:1px solid var(--ember);background:var(--ember);color:var(--ember-ink);cursor:pointer;text-align:left;transition:background .15s ease,border-color .15s ease,transform .15s ease,color .15s ease;min-width:0}
|
||||
.toggle-big:hover{background:var(--ember-hi);border-color:var(--ember-hi);transform:translateY(-1px)}
|
||||
.toggle-big:disabled{opacity:.45;cursor:default;transform:none}
|
||||
.toggle-big .ring{width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(12,12,14,.18);margin-bottom:6px}
|
||||
|
|
@ -201,64 +213,50 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.toggle-big .tl{font-family:var(--head);font-weight:700;font-size:18px;line-height:1.15;white-space:nowrap}
|
||||
.toggle-big .ts{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.06em;opacity:.8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
|
||||
.toggle-big.stop{background:var(--graphite);border-color:var(--line-2);color:var(--bone)}
|
||||
.toggle-big.stop:hover{border-color:var(--ash);background:#1b1b20}
|
||||
.toggle-big.stop:hover{border-color:var(--ash);background:var(--row)}
|
||||
.toggle-big.stop .ring{background:var(--ember-12);color:var(--ember)}
|
||||
.toggle-big.stop .ts{color:var(--molten);opacity:1}
|
||||
.strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--gap-tile)}
|
||||
.strip.three{grid-template-columns:repeat(3,minmax(0,1fr))}
|
||||
.cell{background:var(--graphite);border:1px solid var(--line);border-radius:var(--tile-r);padding:var(--tile-pad);display:flex;flex-direction:column;gap:6px;min-width:0}
|
||||
.cell .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.12em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.cell .v{font-family:var(--head);font-weight:700;font-size:var(--t-num);line-height:1.1;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:baseline;gap:var(--s-2);min-height:1.1em}
|
||||
.cell.ember .v{color:var(--ember)}
|
||||
.cell .v .unit{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);font-weight:500;letter-spacing:.08em}
|
||||
.cell .v.state{text-transform:capitalize;font-size:22px}
|
||||
.cell .v.small{font-size:18px}
|
||||
.cell .v.dim{color:var(--ash)}
|
||||
.cell .s{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.cell.ok .v.state{color:var(--molten)}
|
||||
.cell.bad .v.state{color:var(--ember)}
|
||||
.cell.bad .s{color:var(--ember)}
|
||||
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:var(--gap);align-items:start}
|
||||
.totals{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--s-3);background:var(--graphite);border:1px solid var(--line);border-radius:14px;padding:18px 20px;min-width:0}
|
||||
.tot{display:flex;flex-direction:column;min-width:0;padding-left:var(--s-4);border-left:1px solid var(--line)}
|
||||
.tot:first-child{padding-left:0;border-left:0}
|
||||
.tot .v{font-family:var(--head);font-weight:700;font-size:var(--t-hero);line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em}
|
||||
.tot .v.ember{color:var(--ember)}
|
||||
.tot .v.ask-price{font-family:var(--sans);font-size:var(--t-md);font-weight:600;color:var(--molten);text-decoration:underline;text-underline-offset:3px;cursor:pointer;padding:9px 0 8px}
|
||||
.tot .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.14em;text-transform:uppercase;color:var(--ash);margin-top:4px}
|
||||
.tot .s{font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
|
||||
.card-head{display:flex;justify-content:space-between;align-items:center;gap:var(--s-3);margin-bottom:var(--s-3);flex-wrap:wrap}
|
||||
.stats{display:flex;flex-wrap:wrap;gap:12px 16px;font-size:var(--t-sm);color:var(--ash)}
|
||||
.stats b{color:var(--bone);font-weight:500;font-variant-numeric:tabular-nums}
|
||||
#dag{display:block;width:100%;height:170px;border-radius:12px;background:var(--obsidian)}
|
||||
.legend{display:flex;flex-wrap:wrap;gap:14px 18px;margin-top:var(--s-3);font-size:var(--t-sm);color:var(--ink-2)}
|
||||
.legend span{display:inline-flex;align-items:center;gap:var(--s-2)}
|
||||
.sw{width:12px;height:12px;border-radius:3px;display:inline-block;border:1px solid var(--line-2)}
|
||||
.sw.ember{background:var(--ember);border-color:var(--ember)}
|
||||
.sw.glow{border-color:var(--molten);box-shadow:0 0 8px rgba(255,179,92,.7)}
|
||||
.sw.line{width:18px;height:0;border:0;border-top:1px dashed var(--line-2);border-radius:0}
|
||||
.head-right{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;min-width:0}
|
||||
.stats{font-size:var(--t-sm);color:var(--ash)}
|
||||
#dag{display:block;width:100%;height:110px;border-radius:10px;background:var(--obsidian);margin-bottom:var(--s-3)}
|
||||
.empty{color:var(--ash);font-size:var(--t-base);padding:10px 0}
|
||||
.empty.err{color:var(--ember)}
|
||||
.kv{display:flex;flex-direction:column}
|
||||
.kv>div{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s-3);padding:7px 0;border-bottom:1px solid var(--line)}
|
||||
.kv>div{display:grid;grid-template-columns:120px auto 1fr;align-items:baseline;gap:var(--s-3);padding:7px 0;border-bottom:1px solid var(--line)}
|
||||
.kv>div:last-child{border-bottom:0}
|
||||
.kv .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.1em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.kv .v{font-size:var(--t-md);font-variant-numeric:tabular-nums;color:var(--bone);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}
|
||||
.kv .v{font-size:var(--t-md);font-variant-numeric:tabular-nums;color:var(--bone);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.kv .v.ok{color:var(--molten)}
|
||||
.kv .v.warn{color:var(--ember)}
|
||||
.kv .v.dim{color:var(--ash)}
|
||||
.kv .m{font-size:var(--t-sm);color:var(--ash);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.card .note{margin-top:10px}
|
||||
.card .help+.help{margin-top:6px}
|
||||
.big-word{font-family:var(--head);font-weight:700;font-size:20px;line-height:1.2;margin:2px 0 8px;word-break:break-word}
|
||||
.big-word.ok{color:var(--molten)}
|
||||
.big-word.warn{color:var(--ember)}
|
||||
.big-word.dim{color:var(--ash)}
|
||||
.feed{display:flex;flex-direction:column;font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2);max-height:260px;overflow:auto}
|
||||
.feed>div{display:flex;justify-content:space-between;gap:10px;border-bottom:1px solid var(--line);padding:8px 0;align-items:baseline}
|
||||
.feed{display:flex;flex-direction:column;font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2)}
|
||||
.feed>div{display:flex;justify-content:space-between;gap:10px;border-bottom:1px solid var(--line);padding:7px 0;align-items:baseline}
|
||||
.feed>div:last-child{border-bottom:0}
|
||||
.feed>div>span:first-child{min-width:0}
|
||||
.feed>div>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.feed .t{color:var(--ash);flex:0 0 auto;font-size:var(--t-xs)}
|
||||
.feed .k{color:var(--molten);margin-right:6px}
|
||||
.feed .k.error{color:var(--ember)}
|
||||
.feed .k.warn{color:var(--ember)}
|
||||
.feed .k.block{color:var(--ember)}
|
||||
.feed .k.build{color:var(--ink-2)}
|
||||
.feed .k.error,.feed .k.warn,.feed .k.block{color:var(--ember)}
|
||||
.feed .k.build,.feed .k.info{color:var(--ash)}
|
||||
|
||||
/* the GPU rows on the Mine page: badge, name, numbers, the switch */
|
||||
/* the card rows on Mine (the hero object): badge, name and state, the numbers, Tune, the switch, the chevron;
|
||||
the tune line under; the details under that */
|
||||
.gpu-list{display:flex;flex-direction:column;gap:var(--s-2)}
|
||||
.gpu-line{display:grid;grid-template-columns:44px minmax(160px,1.4fr) repeat(3,minmax(84px,.7fr)) 48px;align-items:center;gap:var(--s-4);padding:12px 14px;border:1px solid var(--line);border-radius:var(--tile-r);background:var(--obsidian);min-width:0}
|
||||
.gpu-line{border:1px solid var(--line);border-radius:var(--row-r);background:var(--row);min-width:0}
|
||||
.gpu-line.off{opacity:.62}
|
||||
.gpu-line .badge{width:44px;height:44px;flex-basis:44px;border-radius:12px}
|
||||
.gpu-line.open{border-color:var(--line-2)}
|
||||
.gl-main{display:grid;grid-template-columns:44px minmax(150px,1.2fr) auto auto;align-items:center;gap:var(--s-4);padding:12px 14px}
|
||||
.gpu-line .who{min-width:0;display:flex;flex-direction:column;gap:3px}
|
||||
.gpu-line .name{font-family:var(--head);font-weight:700;font-size:var(--t-md);line-height:1.25;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
||||
.gpu-line .st{display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
|
||||
|
|
@ -267,46 +265,75 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.gpu-line .st i{width:7px;height:7px;border-radius:50%;background:currentColor;display:inline-block;flex:0 0 7px}
|
||||
.gpu-line .msg{font-size:var(--t-sm);color:var(--ember);line-height:1.4}
|
||||
.gpu-line .msg.dim{color:var(--ash)}
|
||||
.gpu-line .num{display:flex;flex-direction:column;gap:2px;min-width:0}
|
||||
.gpu-line .num .k{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.gpu-line .num .v{font-family:var(--head);font-weight:700;font-size:18px;line-height:1.15;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:baseline;gap:5px}
|
||||
.gpu-line .num .v .unit{font-family:var(--mono);font-size:10px;color:var(--ash);font-weight:500;letter-spacing:.08em}
|
||||
.gpu-line .num .v.hot{color:var(--ember)}
|
||||
.gpu-line .num .v.warm{color:var(--molten)}
|
||||
.gpu-line .num .v.na{color:var(--ash);font-weight:500;font-size:var(--t-md)}
|
||||
.nums{display:flex;gap:var(--s-5);justify-content:flex-end;align-items:flex-start;min-width:0;flex-wrap:wrap}
|
||||
.num{display:flex;flex-direction:column;gap:1px;min-width:56px;align-items:flex-end;text-align:right}
|
||||
.num .v{font-family:var(--head);font-weight:700;font-size:20px;line-height:1.15;white-space:nowrap;display:flex;align-items:baseline;gap:4px}
|
||||
.num .v .unit{font-family:var(--mono);font-size:10px;color:var(--ash);font-weight:500;letter-spacing:.08em}
|
||||
.num .k{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.num .v.hot{color:var(--ember)}
|
||||
.num .v.warm{color:var(--molten)}
|
||||
.gpu-line.on .num.hash .v{color:var(--ember)}
|
||||
.gpu-line .switch{padding:0;justify-self:end}
|
||||
|
||||
/* the Settings page: per-card controls */
|
||||
.set-cards{display:flex;flex-direction:column;gap:var(--s-3);margin-bottom:var(--s-3)}
|
||||
.set-card{border:1px solid var(--line);border-radius:var(--tile-r);background:var(--obsidian);padding:14px 16px;display:flex;flex-direction:column;gap:10px;min-width:0}
|
||||
.set-card .head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||||
.set-card .head .name{font-family:var(--head);font-weight:700;font-size:var(--t-md)}
|
||||
.set-card .head .meta{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash)}
|
||||
.set-card .ctl{display:grid;grid-template-columns:150px 1fr auto;align-items:center;gap:var(--s-3)}
|
||||
.set-card .ctl .k{font-size:var(--t-md);color:var(--bone);font-weight:500}
|
||||
.set-card .ctl .pv{font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2);min-width:110px;text-align:right;white-space:nowrap}
|
||||
.set-card input[type=range]{width:100%;accent-color:var(--ember);margin:0}
|
||||
.set-card .ctl .help{grid-column:1 / -1;margin-top:-4px}
|
||||
.set-card .line{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);display:flex;flex-wrap:wrap;gap:6px 12px;align-items:center}
|
||||
.set-card .line b{color:var(--ink-2);font-weight:500}
|
||||
.set-card .line.ok{color:var(--molten)}
|
||||
.set-card .line.hot{color:var(--ember)}
|
||||
.set-card .line.on{color:var(--molten)}
|
||||
.set-card .line .pin{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);border:1px solid var(--line-2);border-radius:6px;padding:1px 6px}
|
||||
.acts{display:flex;align-items:center;gap:var(--s-3);justify-self:end}
|
||||
.acts .switch{padding:0}
|
||||
.chev-btn{width:30px;height:30px;border-radius:8px;border:1px solid transparent;background:transparent;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:var(--ash)}
|
||||
.chev-btn:hover{border-color:var(--line-2);color:var(--bone)}
|
||||
.chev{width:9px;height:9px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg) translateY(-2px);transition:transform .15s ease;display:inline-block}
|
||||
.open .chev,.chev-btn.open .chev{transform:rotate(225deg) translateY(-2px)}
|
||||
.gl-tune{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;padding:8px 14px 10px 72px;font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2);border-top:1px solid var(--line)}
|
||||
.gl-tune .t{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
|
||||
.gl-tune .n{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.gl-tune.running{color:var(--molten)}
|
||||
.gl-tune.stopped,.gl-tune.paused{color:var(--ember)}
|
||||
.gl-tune .bar{flex-basis:100%;height:3px;border-radius:2px;background:var(--line);overflow:hidden;display:block}
|
||||
.gl-tune .bar b{display:block;height:100%;background:var(--molten);transition:width .5s linear}
|
||||
.gl-details{padding:12px 14px 14px 72px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:10px;user-select:text;-webkit-user-select:text}
|
||||
.gl-details .ctl{display:grid;grid-template-columns:120px 1fr auto;align-items:center;gap:var(--s-3)}
|
||||
.gl-details .ctl .k{font-size:var(--t-md);color:var(--bone);font-weight:500}
|
||||
.gl-details .ctl .pv{font-size:var(--t-sm);color:var(--ink-2);min-width:100px;text-align:right;white-space:nowrap}
|
||||
.gl-details input[type=range]{width:100%;accent-color:var(--ember);margin:0}
|
||||
.gl-details .ctl .help{grid-column:1 / -1;margin-top:-4px}
|
||||
.gl-details .ctl .help:first-of-type{grid-column:2}
|
||||
.gl-details .line{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);display:flex;flex-wrap:wrap;gap:6px 12px;align-items:center}
|
||||
.gl-details .line b{color:var(--ink-2);font-weight:500}
|
||||
.gl-details .line.ok{color:var(--molten)}
|
||||
.gl-details .line.hot{color:var(--ember)}
|
||||
.gl-details .line .pin{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);border:1px solid var(--line-2);border-radius:6px;padding:1px 6px}
|
||||
.curve{font-size:var(--t-sm);font-family:var(--mono);border-collapse:collapse;width:100%}
|
||||
.curve th{text-align:left;font-weight:500;color:var(--ash);font-size:10px;letter-spacing:.1em;text-transform:uppercase;padding:3px 8px 3px 0}
|
||||
.curve td{padding:3px 8px 3px 0;border-top:1px solid var(--line);color:var(--ink-2)}
|
||||
.curve tr.marked td{color:var(--ash);text-decoration:line-through}
|
||||
.ids{display:flex;align-items:center;gap:6px;flex:0 0 auto;justify-self:end}
|
||||
.ids button{width:30px;height:30px;border-radius:8px;border:1px solid var(--line-2);background:transparent;color:var(--bone);cursor:pointer;font-size:16px;line-height:1}
|
||||
.ids button:hover{border-color:var(--ash)}
|
||||
.ids input{width:44px;text-align:center;background:var(--obsidian);border:1px solid var(--line-2);border-radius:8px;padding:5px 4px;color:var(--bone);font-family:var(--mono);font-size:var(--t-base);user-select:text;-webkit-user-select:text}
|
||||
.ids input:focus{outline:none;border-color:var(--ember)}
|
||||
.ids input::-webkit-inner-spin-button,.ids input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
|
||||
.lead-card .lead-row,.lead-row{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s-4)}
|
||||
.lead-text{min-width:0;display:flex;flex-direction:column;gap:6px}
|
||||
.lead-text h3{font-size:var(--t-2xl)}
|
||||
|
||||
/* the tuning card: the goal, its consequence, the schedule, Power control when it is the fix */
|
||||
.goal-row{display:flex;align-items:center;gap:var(--s-4);flex-wrap:wrap;margin-bottom:var(--s-3)}
|
||||
.seg{display:inline-flex;background:var(--obsidian);border:1px solid var(--line);border-radius:10px;padding:3px;gap:2px}
|
||||
.seg-b{border:0;background:transparent;color:var(--ash);font-size:var(--t-base);font-weight:500;padding:6px 14px;border-radius:8px;cursor:pointer;transition:background .15s ease,color .15s ease;white-space:nowrap}
|
||||
.seg-b:hover{color:var(--bone)}
|
||||
.seg-b.on{background:var(--graphite);color:var(--bone);box-shadow:0 1px 3px rgba(0,0,0,.25);font-weight:600}
|
||||
.goal-line{font-size:var(--t-base);color:var(--ink-2);min-width:0}
|
||||
|
||||
/* the node line and the disclosures */
|
||||
.disclose{display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);width:100%;border:0;background:transparent;padding:0;cursor:pointer;text-align:left;color:var(--bone);font-size:var(--t-md);font-weight:500;min-height:32px}
|
||||
.disclose:hover{color:var(--bone)}
|
||||
.disclose .chev{color:var(--ash);flex:0 0 auto;margin-right:4px}
|
||||
.disclose-right{display:flex;align-items:center;gap:var(--s-3);min-width:0}
|
||||
.disclose-right .dim{font-size:var(--t-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;max-width:52ch}
|
||||
.node-line{display:inline-flex;align-items:center;gap:10px;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.node-card.bad .node-line{color:var(--ember)}
|
||||
.card .disclose+.disclose,.card .details+.disclose,.card .help+.disclose,.card .addr-big+.help+.disclose{margin-top:var(--s-3);padding-top:var(--s-3);border-top:1px solid var(--line)}
|
||||
.details{padding-top:var(--s-3);display:flex;flex-direction:column;gap:var(--s-3)}
|
||||
.switch-list{display:flex;flex-direction:column;gap:3px;font-size:var(--t-sm);color:var(--ash);margin-top:8px}
|
||||
.switch-list span{display:flex;justify-content:space-between;gap:12px}
|
||||
.switch-list span.past{opacity:.55}
|
||||
.switch-list span.next{color:var(--molten)}
|
||||
.lead-row{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s-4)}
|
||||
.lead-text{min-width:0;display:flex;flex-direction:column;gap:6px}
|
||||
.lead-text h3{font-size:var(--t-xl)}
|
||||
.addr-big{display:flex;align-items:center;gap:12px;background:var(--obsidian);border:1px solid var(--line-2);border-radius:12px;padding:14px 16px;font-size:var(--t-lg);word-break:break-all;user-select:text;-webkit-user-select:text;margin-bottom:10px}
|
||||
.addr-big span{flex:1;min-width:0;color:var(--molten)}
|
||||
.card.adv{padding:0}
|
||||
|
|
@ -317,22 +344,35 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.card.adv summary .eyebrow{margin-left:0}
|
||||
.card.adv>:not(summary){margin-left:var(--card-pad);margin-right:var(--card-pad)}
|
||||
.card.adv>:last-child{margin-bottom:var(--card-pad)}
|
||||
.job-history table{margin-top:var(--s-2)}
|
||||
table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
||||
.job-history th{text-align:left;font-weight:500;color:var(--ash);font-size:var(--t-xs);padding:4px 6px 4px 0;font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase}
|
||||
.job-history td{padding:6px 6px 6px 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{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:var(--s-2)}
|
||||
.clock-card.warn{border-color:var(--molten-40);background:rgba(255,179,92,.06)}
|
||||
#s-jobs-history th{text-align:left;font-weight:500;color:var(--ash);font-size:var(--t-xs);padding:4px 6px 4px 0;font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase}
|
||||
#s-jobs-history td{padding:6px 6px 6px 0;border-top:1px solid var(--line);vertical-align:top}
|
||||
#s-jobs-history tr.failed td,#s-jobs-history tr.timeout td,#s-jobs-history tr.aborted td{color:var(--ember)}
|
||||
#s-jobs-history tr.running td{color:var(--molten)}
|
||||
.clock-card{border:1px solid var(--ember-40);background:var(--ember-12);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:var(--s-2)}
|
||||
.clock-card.warn{border-color:var(--molten-40);background:var(--molten-10)}
|
||||
.clock-msg{font-size:var(--t-md);color:var(--bone);line-height:1.45}
|
||||
.clock-card .note{margin-top:0}
|
||||
|
||||
/* earnings: money first */
|
||||
.money{display:flex;flex-direction:column;margin-bottom:var(--s-2)}
|
||||
.money-row{display:flex;align-items:baseline;gap:var(--s-3);padding:8px 0;border-bottom:1px solid var(--line);flex-wrap:wrap}
|
||||
.money-row:last-child{border-bottom:0}
|
||||
.money-row .v{font-family:var(--head);font-weight:700;font-size:var(--t-hero);line-height:1.1;white-space:nowrap;letter-spacing:-.01em;min-width:200px}
|
||||
.money-row .v.small{font-size:var(--t-num)}
|
||||
.money-row .s{font-size:var(--t-base);color:var(--ash);min-width:0}
|
||||
|
||||
/* prove */
|
||||
.tier-list{list-style:none;margin:var(--s-3) 0 0;padding:0;display:flex;flex-direction:column;gap:6px;font-size:var(--t-md);color:var(--ink-2)}
|
||||
.tier-list li{display:flex;gap:10px;align-items:baseline}
|
||||
.tier-list li::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--ember);flex:0 0 6px;position:relative;top:-2px}
|
||||
#pv-line{margin-top:var(--s-3)}
|
||||
|
||||
/* address options */
|
||||
.options{display:flex;flex-direction:column;gap:var(--s-3);margin-top:6px}
|
||||
.option{display:flex;gap:var(--s-4);align-items:flex-start;background:var(--graphite);border:1px solid var(--line);border-radius:var(--card-r);padding:20px 22px;cursor:pointer;transition:border-color .15s ease,background .15s ease}
|
||||
.option:hover{border-color:var(--line-2)}
|
||||
.option.on{border-color:var(--ember);background:#1A1614}
|
||||
.option.on{border-color:var(--ember);background:var(--ember-12)}
|
||||
.option input[type=radio]{position:absolute;opacity:0;width:0;height:0}
|
||||
.option .radio{width:20px;height:20px;border-radius:50%;border:2px solid var(--line-2);flex:0 0 20px;margin-top:2px;position:relative}
|
||||
.option.on .radio{border-color:var(--ember)}
|
||||
|
|
@ -342,15 +382,15 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
.option .s{font-size:var(--t-md);color:var(--ash);line-height:1.5}
|
||||
.tag{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--molten);border:1px solid var(--molten-40);border-radius:999px;padding:2px 8px}
|
||||
.addr-input{width:100%;margin-top:var(--s-2);background:var(--obsidian);border:1px solid var(--line-2);border-radius:10px;padding:12px 14px;color:var(--bone);font-size:var(--t-md);letter-spacing:.02em;user-select:text;-webkit-user-select:text}
|
||||
.addr-input.short{width:100px;flex:0 0 100px}
|
||||
.addr-input:focus{outline:none;border-color:var(--ember)}
|
||||
.option:not(.on) .addr-input{display:none}
|
||||
.err{font-size:var(--t-base);color:var(--ember)}
|
||||
|
||||
/* the update card (app.js, UpdateCard; the wallet carries the same block): the mark with its ring, the name, one
|
||||
line, up to three note lines, the size, Install now and Later. Over the key sheet and the pages. */
|
||||
.upd-wrap{position:fixed;inset:0;z-index:35;background:rgba(12,12,14,.72);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px;animation:fade .25s ease}
|
||||
/* the update card (app.js, UpdateCard; the wallet carries the same block) */
|
||||
.upd-wrap{position:fixed;inset:0;z-index:35;background:var(--scrim);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px;animation:fade .25s ease}
|
||||
@keyframes fade{from{opacity:0}to{opacity:1}}
|
||||
.upd-card{position:relative;width:100%;max-width:500px;max-height:calc(100vh - 48px);overflow:auto;background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:34px 32px 28px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--s-2);box-shadow:0 30px 80px rgba(0,0,0,.6),0 0 0 1px rgba(242,84,27,.08);animation:rise .3s ease;outline:none}
|
||||
.upd-card{position:relative;width:100%;max-width:500px;max-height:calc(100vh - 48px);overflow:auto;background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:34px 32px 28px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--s-2);box-shadow:0 30px 80px var(--shadow),0 0 0 1px rgba(242,84,27,.08);animation:rise .3s ease;outline:none}
|
||||
.upd-card::before{content:"";position:absolute;left:50%;top:-80px;width:360px;height:260px;transform:translateX(-50%);border-radius:50%;background:radial-gradient(circle,rgba(242,84,27,.22) 0,rgba(242,84,27,0) 62%);pointer-events:none}
|
||||
.upd-mark{position:relative;width:96px;height:96px;display:flex;align-items:center;justify-content:center;margin-bottom:var(--s-3)}
|
||||
.upd-mark img{position:relative;display:block;filter:drop-shadow(0 6px 18px rgba(242,84,27,.35))}
|
||||
|
|
@ -381,8 +421,8 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
@media (max-height:620px){.upd-card{padding:24px 24px 20px}.upd-mark{width:72px;height:72px;margin-bottom:var(--s-2)}.upd-mark img{width:32px;height:32px}.upd-name{font-size:20px}}
|
||||
|
||||
/* sheet (the key) */
|
||||
.sheet-wrap{position:fixed;inset:0;z-index:30;background:rgba(12,12,14,.72);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
.sheet{background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:32px;max-width:640px;width:100%;max-height:calc(100vh - 48px);overflow:auto;display:flex;flex-direction:column;gap:14px;box-shadow:0 30px 80px rgba(0,0,0,.6);animation:rise .3s ease}
|
||||
.sheet-wrap{position:fixed;inset:0;z-index:30;background:var(--scrim);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
.sheet{background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:32px;max-width:640px;width:100%;max-height:calc(100vh - 48px);overflow:auto;display:flex;flex-direction:column;gap:14px;box-shadow:0 30px 80px var(--shadow);animation:rise .3s ease}
|
||||
.sheet .sub{font-size:var(--t-lg);color:var(--ink-2)}
|
||||
.field{display:flex;flex-direction:column;gap:var(--s-2);margin-top:6px}
|
||||
.field .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.12em;text-transform:uppercase;color:var(--ash)}
|
||||
|
|
@ -398,13 +438,13 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
|
||||
/* rows, switches */
|
||||
.row{display:flex;gap:10px;align-items:center;min-width:0}
|
||||
.row.between{justify-content:space-between}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.row .addr-input{margin-top:0;min-width:0}
|
||||
.switch{display:flex;align-items:center;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:8px 0 2px;line-height:1.35}
|
||||
.switch{display:flex;align-items:flex-start;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:9px 0;line-height:1.4}
|
||||
.switch+.switch{border-top:1px solid var(--line)}
|
||||
.switch input{position:absolute;opacity:0;width:0;height:0}
|
||||
.switch .track{width:40px;height:22px;border-radius:999px;background:var(--line-2);position:relative;flex:0 0 40px;transition:background .15s ease}
|
||||
.switch .track::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:var(--bone);transition:transform .15s ease}
|
||||
.switch .track{width:40px;height:22px;border-radius:999px;background:var(--line-2);position:relative;flex:0 0 40px;transition:background .15s ease;margin-top:1px}
|
||||
.switch .track::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#F4F1EC;transition:transform .15s ease}
|
||||
.switch input:checked+.track{background:var(--ember)}
|
||||
.switch input:checked+.track::after{transform:translateX(18px)}
|
||||
.switch input:focus-visible+.track{outline:2px solid var(--ember);outline-offset:3px}
|
||||
|
|
@ -412,10 +452,12 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
.switch.lg .track{width:52px;height:30px;flex-basis:52px}
|
||||
.switch.lg .track::after{width:24px;height:24px}
|
||||
.switch.lg input:checked+.track::after{transform:translateX(22px)}
|
||||
.switch+.help{margin-bottom:6px}
|
||||
.sw-text{min-width:0}
|
||||
.sw-text b{font-weight:600}
|
||||
.sw-text .dim{font-size:var(--t-base)}
|
||||
|
||||
/* log drawer */
|
||||
.drawer{position:fixed;left:0;right:0;bottom:0;height:0;overflow:hidden;background:var(--obsidian);border-top:1px solid var(--line);z-index:21;transition:height .2s ease;display:flex;flex-direction:column;box-shadow:0 -12px 30px rgba(0,0,0,.35)}
|
||||
.drawer{position:fixed;left:0;right:0;bottom:0;height:0;overflow:hidden;background:var(--obsidian);border-top:1px solid var(--line);z-index:21;transition:height .2s ease;display:flex;flex-direction:column;box-shadow:0 -12px 30px var(--shadow)}
|
||||
body.has-rail .drawer{left:var(--rail)}
|
||||
body.drawer-open .drawer{height:var(--drawer-h)}
|
||||
body.drawer-drag .drawer{transition:none}
|
||||
|
|
@ -450,8 +492,8 @@ body.drawer-drag main{pointer-events:none}
|
|||
.log-ruler .t1{right:0;border-radius:0 0 0 6px}
|
||||
.log-ruler .mk{position:absolute;top:4px;bottom:4px;width:2px;margin-left:-1px;background:var(--ember);opacity:.9;pointer-events:none}
|
||||
.log-ruler .mk.swap{background:var(--molten)}
|
||||
.log-ruler .win{position:absolute;top:0;bottom:0;background:rgba(244,241,236,.14);border-left:1px solid rgba(244,241,236,.35);border-right:1px solid rgba(244,241,236,.35);min-width:3px;pointer-events:none}
|
||||
.log-ruler:hover .win{background:rgba(244,241,236,.2)}
|
||||
.log-ruler .win{position:absolute;top:0;bottom:0;background:rgba(127,127,127,.14);border-left:1px solid rgba(127,127,127,.35);border-right:1px solid rgba(127,127,127,.35);min-width:3px;pointer-events:none}
|
||||
.log-ruler:hover .win{background:rgba(127,127,127,.2)}
|
||||
.log-view{position:relative;flex:1;overflow:auto;overscroll-behavior:contain;user-select:text;-webkit-user-select:text;contain:strict}
|
||||
.log-pad{position:relative;min-height:100%}
|
||||
.log{margin:0;position:absolute;top:0;left:0;right:0;padding:6px var(--gutter);font-size:var(--t-sm);line-height:19px;color:var(--ink-2);white-space:pre;will-change:transform}
|
||||
|
|
@ -459,7 +501,7 @@ body.drawer-drag main{pointer-events:none}
|
|||
.log-view.wrap .log{position:relative;padding-bottom:12px}
|
||||
.log-view.wrap .log .ln{height:auto;white-space:pre-wrap;word-break:break-all;overflow:visible}
|
||||
.log .src{color:var(--ash)}
|
||||
.log .src.node{color:var(--node-blue)}
|
||||
.log .src.node{color:#7FA7C9}
|
||||
.log .src.miner1,.log .src.miner2,.log .src.miner3,.log .src.miner4,.log .src.miner5,.log .src.miner6,.log .src.miner7,.log .src.miner8{color:var(--molten)}
|
||||
.log .src.app{color:var(--ember)}
|
||||
.log .src.watch{color:var(--ash)}
|
||||
|
|
@ -467,22 +509,18 @@ body.drawer-drag main{pointer-events:none}
|
|||
.log .ln.hit{background:rgba(255,179,92,.18);box-shadow:inset 3px 0 0 var(--molten);margin:0 calc(-1 * var(--gutter));padding:0 var(--gutter)}
|
||||
.log mark{background:rgba(255,179,92,.3);color:var(--bone);border-radius:2px;padding:0 1px}
|
||||
.log-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--ash);font-size:var(--t-sm);padding:0 var(--gutter);text-align:center}
|
||||
.log-jump{position:absolute;right:calc(var(--gutter) + 14px);bottom:14px;background:var(--graphite);border-color:var(--molten-40);color:var(--molten);box-shadow:0 8px 24px rgba(0,0,0,.5);z-index:2;gap:6px;padding:6px 12px}
|
||||
.log-jump:hover{background:#1c1a18;border-color:var(--molten)}
|
||||
.log-jump{position:absolute;right:calc(var(--gutter) + 14px);bottom:14px;background:var(--graphite);border-color:var(--molten-40);color:var(--molten);box-shadow:0 8px 24px var(--shadow);z-index:2;gap:6px;padding:6px 12px}
|
||||
.log-jump:hover{border-color:var(--molten)}
|
||||
|
||||
.toast{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);background:var(--graphite);border:1px solid var(--line-2);border-radius:10px;padding:10px 16px;font-size:var(--t-base);z-index:40;box-shadow:0 10px 30px rgba(0,0,0,.5);max-width:min(90vw,520px);text-align:center}
|
||||
.toast{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);background:var(--graphite);border:1px solid var(--line-2);border-radius:10px;padding:10px 16px;font-size:var(--t-base);z-index:40;box-shadow:0 10px 30px var(--shadow);max-width:min(90vw,520px);text-align:center}
|
||||
body.has-rail .toast{left:calc(50% + var(--rail) / 2)}
|
||||
body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)}
|
||||
|
||||
/* narrow windows (the 900 px floor): the rail folds to icons with small labels, the hero row and the strips fold to
|
||||
two columns, the two-column grid to one, the GPU rows drop a number */
|
||||
/* narrow windows: 1180 the hero wraps; 1000 the rail folds to icons; 900 the row's cells wrap; 720 a bottom tab bar */
|
||||
@media (max-width:1180px){
|
||||
.hero-row{grid-template-columns:1fr 1fr}
|
||||
.toggle-big{grid-row:span 1}
|
||||
.strip{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.strip.three{grid-template-columns:repeat(3,minmax(0,1fr))}
|
||||
.grid2{grid-template-columns:1fr}
|
||||
.top-status{max-width:220px}
|
||||
.hero-row{grid-template-columns:1fr}
|
||||
.totals{grid-template-columns:repeat(4,minmax(0,1fr))}
|
||||
.nums{gap:var(--s-4)}
|
||||
}
|
||||
@media (max-width:1000px){
|
||||
:root{--rail:76px;--gutter:var(--s-5)}
|
||||
|
|
@ -497,29 +535,73 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)}
|
|||
.rail-status{display:none}
|
||||
.rail-version{text-align:center;padding:8px 0 0;font-size:10px;white-space:nowrap}
|
||||
.rail-version .chain{display:none}
|
||||
.gpu-line{grid-template-columns:44px minmax(140px,1.4fr) repeat(2,minmax(80px,.7fr)) 48px}
|
||||
.gpu-line .num.power{display:none}
|
||||
.page-sub{display:none}
|
||||
.top-status{display:none}
|
||||
.strip.three{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.set-card .ctl{grid-template-columns:120px 1fr auto}
|
||||
.gl-main{grid-template-columns:44px minmax(140px,1fr) auto}
|
||||
.nums{grid-column:2;justify-content:flex-start}
|
||||
.acts{grid-column:3;grid-row:1}
|
||||
.gl-tune,.gl-details{padding-left:14px}
|
||||
.kv>div{grid-template-columns:110px auto 1fr}
|
||||
}
|
||||
@media (max-width:860px){
|
||||
.three{grid-template-columns:1fr}
|
||||
h1{font-size:40px}
|
||||
.totals{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:var(--s-3)}
|
||||
.tot:nth-child(3){padding-left:0;border-left:0}
|
||||
.money-row .v{min-width:0}
|
||||
}
|
||||
/* short windows (the 600 px floor): tighter paddings, a lower hero, a smaller canvas, so the drawer always has room */
|
||||
@media (max-width:720px){
|
||||
:root{--rail:0px;--gutter:var(--s-4);--bottom:64px}
|
||||
body.has-rail .rail{top:auto;bottom:0;left:0;right:0;width:auto;height:64px;flex-direction:row;align-items:center;border-right:0;border-top:1px solid var(--line);padding:0 var(--s-2)}
|
||||
.rail-brand{display:none}
|
||||
.rail-nav{flex-direction:row;flex:1;gap:0;justify-content:space-around}
|
||||
.nav{min-height:56px;padding:6px 2px;font-size:10px;border-radius:10px;flex:1;max-width:120px}
|
||||
.nav.on::before{display:none}
|
||||
.rail-foot{margin-top:0;padding-top:0;border-top:0;flex-direction:row;gap:0}
|
||||
.rail-foot .nav.small{min-height:56px;flex:0 0 auto;padding:6px 8px}
|
||||
.rail-version{display:none}
|
||||
body.has-rail .top,body.has-rail .notices,body.has-rail main,body.has-rail .drawer,body.has-rail .ask-wrap{left:0}
|
||||
body.has-rail main{bottom:var(--bottom)}
|
||||
body.has-rail .toast{left:50%;bottom:calc(var(--bottom) + 12px)}
|
||||
body.drawer-open .drawer{bottom:var(--bottom)}
|
||||
.ask-wrap{bottom:var(--bottom)}
|
||||
.top{padding:0 var(--s-4)}
|
||||
.page-title h1{font-size:17px}
|
||||
.pill{min-width:0}
|
||||
.hero-row{grid-template-columns:1fr}
|
||||
.toggle-big{min-height:88px}
|
||||
.gl-main{grid-template-columns:44px 1fr;gap:var(--s-3);padding:12px}
|
||||
.nums{grid-column:1 / -1;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-2) var(--s-3);justify-items:start}
|
||||
.acts{grid-column:1 / -1;grid-row:auto;justify-self:end}
|
||||
.gpu-line .name{font-size:var(--t-base)}
|
||||
.num{align-items:flex-start;text-align:left}
|
||||
.acts{gap:var(--s-2)}
|
||||
.gl-tune,.gl-details{padding-left:12px;padding-right:12px}
|
||||
.gl-details .ctl{grid-template-columns:1fr;gap:6px}
|
||||
.gl-details .ctl .help:first-of-type{grid-column:1}
|
||||
.ids{justify-self:start}
|
||||
.kv>div{grid-template-columns:96px 1fr;gap:4px var(--s-3)}
|
||||
.kv .m{grid-column:1 / -1;white-space:normal}
|
||||
.goal-row{align-items:flex-start;flex-direction:column}
|
||||
.seg{width:100%}
|
||||
.seg-b{flex:1;padding:6px 8px}
|
||||
.indent{margin-left:0}
|
||||
.disclose-right .dim{display:none}
|
||||
.lead-row{flex-direction:column}
|
||||
.step{padding:32px 0}
|
||||
.money-row .v{font-size:var(--t-num)}
|
||||
.drawer-head{padding-left:var(--s-4);padding-right:var(--s-4)}
|
||||
.log,.log-ruler .t0,.log-ruler .t1{padding-left:var(--s-4);padding-right:var(--s-4)}
|
||||
}
|
||||
/* short windows (the 600 px floor): tighter paddings, a smaller canvas, so the drawer always has room */
|
||||
@media (max-height:700px){
|
||||
:root{--card-pad:18px;--tile-pad:14px 16px;--gap:var(--s-4)}
|
||||
:root{--card-pad:18px;--gap:var(--s-3)}
|
||||
#screen-dashboard{padding:16px 0 20px}
|
||||
#dag{height:140px}
|
||||
#dag{height:90px}
|
||||
.hero{gap:var(--s-3);padding:var(--s-5) 0 var(--s-6)}
|
||||
.coin-wrap{width:104px;height:104px}
|
||||
.coin{width:104px;height:104px}
|
||||
.mark-wrap{width:88px;height:88px}
|
||||
h1{font-size:40px}
|
||||
.lead{font-size:var(--t-xl)}
|
||||
.lead{font-size:var(--t-lg)}
|
||||
.step{padding:32px 0 32px}
|
||||
.feed{max-height:200px}
|
||||
.drawer-head{padding-bottom:6px}
|
||||
.toggle-big{min-height:96px}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Igneum Miner</title>
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="color-scheme" content="dark light">
|
||||
<link rel="icon" href="mark.svg" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="app.css">
|
||||
</head>
|
||||
<body class="phase-welcome" data-phase="welcome" data-page="mine">
|
||||
|
||||
<!-- the rail: one button per section (miner-ui-2). Shown on the dashboard; the setup screens have no rail. -->
|
||||
<!-- the rail: four sections (miner-ui-3: Mine, Earnings, Prove, Settings), Logs and Quit in the foot. Under 720 px it
|
||||
is a bottom tab bar. The setup screens have no rail. -->
|
||||
<aside class="rail" id="rail" hidden>
|
||||
<div class="rail-brand">
|
||||
<img src="mark.svg" width="28" height="28" alt="">
|
||||
|
|
@ -18,11 +19,9 @@
|
|||
</div>
|
||||
<nav class="rail-nav" id="rail-nav" aria-label="Sections">
|
||||
<button class="nav on" data-page="mine" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8z"/></svg><span>Mine</span></button>
|
||||
<button class="nav" data-page="earnings"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 15h2"/></svg><span>Earnings</span></button>
|
||||
<button class="nav" data-page="prove"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 4 5v6c0 5 3.4 9.4 8 11 4.6-1.6 8-6 8-11V5l-8-3z"/><path d="m9 12 2 2 4-4"/></svg><span>Prove</span></button>
|
||||
<button class="nav" data-page="rewards"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 15h2"/></svg><span>Rewards</span></button>
|
||||
<button class="nav" data-page="node"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c3 3.5 3 14.5 0 18M12 3c-3 3.5-3 14.5 0 18"/></svg><span>Node</span></button>
|
||||
<button class="nav" data-page="updates"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v11M7 10l5 5 5-5"/><path d="M4 19h16"/></svg><span>Updates</span><i class="nav-dot" id="nav-updates-dot" hidden></i></button>
|
||||
<button class="nav" data-page="settings"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></svg><span>Settings</span></button>
|
||||
<button class="nav" data-page="settings"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></svg><span>Settings</span><i class="nav-dot" id="nav-updates-dot" hidden></i></button>
|
||||
</nav>
|
||||
<div class="rail-foot">
|
||||
<div class="rail-status mono" id="rail-status"></div>
|
||||
|
|
@ -42,13 +41,11 @@
|
|||
<span class="page-sub" id="page-sub"></span>
|
||||
</div>
|
||||
<div class="top-right">
|
||||
<span class="top-status mono" id="top-status"></span>
|
||||
<div class="pill" id="pill"><span class="dot"></span><span id="pill-text">starting</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- the status strip: one notice at a time (updates, remote jobs, the clock), the most important first; app.js fills
|
||||
it from the state (Notices) and the content below moves once when it appears or goes. -->
|
||||
<!-- the status strip: one notice at a time (app.js, Notices) -->
|
||||
<div class="notices" id="notices" hidden>
|
||||
<div class="notice" id="notice" role="status" aria-live="polite">
|
||||
<span class="notice-text" id="notice-text"></span>
|
||||
|
|
@ -59,36 +56,45 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- the quit question, in place of a dialog (the viewer never shows one) -->
|
||||
<div class="ask-wrap" id="ask-quit" hidden>
|
||||
<div class="ask">
|
||||
<span class="ask-text">Quit Igneum Miner? Mining stops first, then the node.</span>
|
||||
<button class="btn small primary" id="ask-quit-yes">Quit</button>
|
||||
<button class="btn small ghost" id="ask-quit-no">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main id="main">
|
||||
|
||||
<!-- 1. welcome -->
|
||||
<section class="screen" id="screen-welcome">
|
||||
<div class="hero">
|
||||
<div class="coin-wrap"><img class="coin" src="coin.png" width="148" height="148" alt=""></div>
|
||||
<div class="mark-wrap"><img src="mark.svg" width="72" height="72" alt=""></div>
|
||||
<div class="eyebrow ember">devnet v4 · nothing is bought or sold</div>
|
||||
<h1>Igneum Miner</h1>
|
||||
<p class="lead">Mined by GPUs. Proven by fire. This app runs a node on this machine and mines Igneum with your graphics card.</p>
|
||||
<p class="lead">Runs a node and mines Igneum with your graphics card. Set up in two steps.</p>
|
||||
<div class="three">
|
||||
<div class="tile">
|
||||
<div class="k">01</div>
|
||||
<div class="t">Runs a node</div>
|
||||
<div class="s">Syncs the chain from the seed node, usually under a minute.</div>
|
||||
<div class="t">Runs in the background</div>
|
||||
<div class="s">The window can close. The miner keeps going from the menu bar or the tray.</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="k">02</div>
|
||||
<div class="t">Mines on your GPU</div>
|
||||
<div class="s">A new program every hour. The next one compiles while you mine.</div>
|
||||
<div class="t">Uses your graphics card</div>
|
||||
<div class="s">Each card is tuned for hashes per watt by itself. You can tune again any time.</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="k">03</div>
|
||||
<div class="t">Pays your address</div>
|
||||
<div class="s">Block rewards go to an EVM address you choose or one made here.</div>
|
||||
<div class="t">Pays an address you own</div>
|
||||
<div class="s">Every block this machine finds pays one address: one made here, or one you paste.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<button class="btn primary big" id="btn-begin">Get started</button>
|
||||
</div>
|
||||
<p class="seedline mono">Nobody from Igneum will ever ask for your seed.</p>
|
||||
<p class="seedline mono">Nobody from Igneum will ever ask for your key.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -96,20 +102,20 @@
|
|||
<section class="screen" id="screen-cards">
|
||||
<div class="step">
|
||||
<div class="eyebrow">step 1 of 2</div>
|
||||
<h2>Your GPU</h2>
|
||||
<h2>Your graphics card</h2>
|
||||
<p class="sub" id="cards-sub">Asking the graphics cards to report in.</p>
|
||||
<div class="cards" id="cards-list">
|
||||
<div class="card detecting" id="cards-detecting">
|
||||
<div class="spinner"></div>
|
||||
<div>
|
||||
<div class="t">Detecting</div>
|
||||
<div class="s mono" id="detect-text">loading the Metal worker</div>
|
||||
<div class="s mono" id="detect-text">asking the graphics cards to report in</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note" id="cards-note" hidden></p>
|
||||
<p class="note" id="cards-power" hidden>Igneum caps each NVIDIA card's power at 80% of its default limit to keep it stable. This needs administrator rights once, when mining starts; the limit goes back to what it was on quit. Settings has a slider per card.</p>
|
||||
<p class="note" id="cards-help" hidden>Each card you switch on gets its own worker. An integrated GPU is off by default: it is slow and shares the machine's memory.</p>
|
||||
<p class="note" id="cards-power" hidden>NVIDIA cards start at 80% of their power limit, which keeps them stable. Windows asks for administrator rights once for that. The card row has a slider.</p>
|
||||
<p class="note" id="cards-help" hidden>An integrated GPU is off by default: it is slow and shares the machine's memory.</p>
|
||||
<div class="cta">
|
||||
<button class="btn primary" id="btn-cards-next" disabled>Continue</button>
|
||||
<button class="btn ghost" id="btn-cards-retry" hidden>Detect again</button>
|
||||
|
|
@ -122,14 +128,14 @@
|
|||
<div class="step">
|
||||
<div class="eyebrow">step 2 of 2</div>
|
||||
<h2>Where should rewards go?</h2>
|
||||
<p class="sub">Every block this machine finds pays one EVM address. Pick one way. One block in 100 pays the miner software's dev fee; Settings turns it off.</p>
|
||||
<p class="sub">Every block this machine finds pays one address. Pick one way.</p>
|
||||
<div class="options">
|
||||
<label class="option on" id="opt-generate">
|
||||
<input type="radio" name="mode" value="generate" checked>
|
||||
<span class="radio"></span>
|
||||
<span class="body">
|
||||
<span class="t">Make me an address <span class="tag">recommended</span></span>
|
||||
<span class="s">A secp256k1 key is made on this machine and stored in the app folder with the file locked to your user. You see the key once, to save it.</span>
|
||||
<span class="s">A key is made on this machine and stored in the app folder, readable by your user only. You see it once, to save it.</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="option" id="opt-paste">
|
||||
|
|
@ -137,98 +143,174 @@
|
|||
<span class="radio"></span>
|
||||
<span class="body">
|
||||
<span class="t">Use my own address</span>
|
||||
<span class="s">Paste an EVM address you control. 0x followed by 40 hex characters.</span>
|
||||
<span class="s">Paste an Ethereum-style address you control: 0x and 40 characters.</span>
|
||||
<input type="text" class="addr-input mono" id="addr-input" placeholder="0x" spellcheck="false" autocomplete="off">
|
||||
<span class="err" id="addr-err" hidden>That is not an EVM address. 0x followed by 40 hex characters.</span>
|
||||
<span class="err" id="addr-err" hidden>That is not an address. 0x followed by 40 hex characters.</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="err" id="start-blocked" hidden></p>
|
||||
<div class="cta">
|
||||
<button class="btn primary big" id="btn-start">Start mining</button>
|
||||
<button class="btn primary big" id="btn-start">Continue</button>
|
||||
<button class="btn ghost" id="btn-address-back">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. the dashboard: six pages behind the rail -->
|
||||
<!-- 4. the dashboard: four pages behind the rail -->
|
||||
<section class="screen" id="screen-dashboard">
|
||||
|
||||
<!-- Mine -->
|
||||
<section class="page" id="page-mine" data-page="mine">
|
||||
<div class="clock-card" id="m-clock" hidden>
|
||||
<p class="clock-msg" id="m-clock-msg"></p>
|
||||
<div class="row"><button class="btn small primary" id="m-clock-sync">Sync clock</button><span class="note mono small" id="m-clock-result"></span></div>
|
||||
<p class="note" id="m-clock-hint"></p>
|
||||
</div>
|
||||
|
||||
<div class="hero-row">
|
||||
<button class="toggle-big" id="btn-toggle" disabled>
|
||||
<span class="ring"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v9"/><path d="M6.5 6.5a8 8 0 1 0 11 0"/></svg></span>
|
||||
<span class="tl" id="btn-toggle-text">Start mining</span>
|
||||
<span class="ts" id="btn-toggle-sub">waiting for the engine</span>
|
||||
</button>
|
||||
<div class="cell big ember">
|
||||
<div class="k">hash rate</div>
|
||||
<div class="v"><span id="d-hash">0.0</span><span class="unit">MH/s</span></div>
|
||||
<div class="s" id="d-hash-sub">waiting for the worker</div>
|
||||
</div>
|
||||
<div class="cell big">
|
||||
<div class="k">blocks found</div>
|
||||
<div class="v" id="d-blocks">0</div>
|
||||
<div class="s" id="d-blocks-sub">accepted by the node</div>
|
||||
</div>
|
||||
<div class="cell big">
|
||||
<div class="k">next program</div>
|
||||
<div class="v" id="d-eta">--:--</div>
|
||||
<div class="s" id="d-eta-sub">waiting for the node</div>
|
||||
<div class="totals" id="totals">
|
||||
<div class="tot"><span class="v ember" id="t-hash">0</span><span class="k">MH/s</span><span class="s" id="t-hash-sub">waiting</span></div>
|
||||
<div class="tot" id="t-power-cell"><span class="v" id="t-power">0</span><span class="k">W</span><span class="s" id="t-power-sub">drawn now</span></div>
|
||||
<div class="tot" id="t-money-cell"><span class="v" id="t-money">£0.00</span><span class="k">a day</span><span class="s" id="t-money-sub">electricity</span></div>
|
||||
<div class="tot"><span class="v" id="t-blocks">0</span><span class="k">blocks</span><span class="s" id="t-blocks-sub">this run</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Your GPUs</h3><div class="eyebrow" id="d-cards-eyebrow">detecting</div></div>
|
||||
<div class="card-head"><h3>Your cards</h3><div class="head-right"><span class="eyebrow" id="d-cards-eyebrow">detecting</span><button class="btn small" id="btn-tune-all" hidden>Tune all</button></div></div>
|
||||
<div class="gpu-list" id="d-cards"><div class="empty">Waiting for the engine.</div></div>
|
||||
<p class="note" id="d-cards-note" hidden></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<h3>Your blocks</h3>
|
||||
<div class="stats mono"><span>last 10 min <b id="d-found-10">0</b></span><span>last hour <b id="d-found-60">0</b></span><span>dev fee <b id="d-fee">0</b></span><span>chain <b id="d-chain-blocks">0</b></span></div>
|
||||
<div class="card" id="tune-card">
|
||||
<div class="card-head"><h3>Tuning</h3><span class="eyebrow" id="tune-eyebrow">Ember Tune</span></div>
|
||||
<div class="goal-row">
|
||||
<div class="seg" id="goal-seg" role="radiogroup" aria-label="Tuning goal">
|
||||
<button class="seg-b" data-goal="efficiency" role="radio" aria-checked="false">Efficiency</button>
|
||||
<button class="seg-b" data-goal="balanced" role="radio" aria-checked="false">Balanced</button>
|
||||
<button class="seg-b" data-goal="rate" role="radio" aria-checked="false">Maximum</button>
|
||||
</div>
|
||||
<span class="goal-line" id="goal-line"></span>
|
||||
</div>
|
||||
<p class="line-text" id="tune-schedule"></p>
|
||||
<label class="switch" id="m-power-row" hidden><input type="checkbox" id="m-power-control"><span class="track"></span><span class="sw-text"><b>Power control</b> <span class="dim">Lets the app set NVIDIA power and clock limits. Windows asks for administrator rights once.</span></span></label>
|
||||
</div>
|
||||
|
||||
<div class="card node-card" id="node-card">
|
||||
<button class="disclose" id="node-toggle" aria-expanded="false" aria-controls="node-details">
|
||||
<span class="node-line"><i class="dot" id="node-dot"></i><span id="node-line-text">Node starting</span></span>
|
||||
<span class="disclose-right"><span class="dim" id="node-sub"></span><span class="chev" aria-hidden="true"></span></span>
|
||||
</button>
|
||||
<div class="details" id="node-details" hidden>
|
||||
<div class="kv">
|
||||
<div><span class="k">state</span><span class="v mono" id="n-state-v"></span><span class="m" id="n-state-m"></span></div>
|
||||
<div><span class="k">height</span><span class="v mono" id="n-blocks">0</span><span class="m" id="n-blocks-sub">blocks this node holds</span></div>
|
||||
<div><span class="k">peers</span><span class="v mono" id="n-peers">0</span><span class="m" id="n-peers-sub">other nodes it talks to</span></div>
|
||||
<div><span class="k">version</span><span class="v mono" id="n-version">--</span><span class="m" id="d-node-net">devnet v4</span></div>
|
||||
<div><span class="k">headers</span><span class="v mono" id="n-headers">0</span><span class="m">headers arrive before blocks</span></div>
|
||||
<div><span class="k">DAA score</span><span class="v mono" id="n-daa">0</span><span class="m">blocks the whole network has made</span></div>
|
||||
<div><span class="k">difficulty</span><span class="v mono" id="n-diff">0</span><span class="m">how hard the next block is to find</span></div>
|
||||
<div><span class="k">tips</span><span class="v mono" id="n-tips">0</span><span class="m">open ends of the block DAG right now</span></div>
|
||||
<div><span class="k">blue score</span><span class="v mono" id="n-blue">0</span><span class="m">blocks on the agreed main chain</span></div>
|
||||
<div><span class="k">next program</span><span class="v mono" id="d-eta">--:--</span><span class="m" id="d-eta-sub">the hourly mining program</span></div>
|
||||
<div><span class="k">last lock</span><span class="v mono" id="f-lock">none yet</span><span class="m" id="f-note">a point the miners agreed can never be undone</span></div>
|
||||
<div><span class="k">votes sent</span><span class="v mono" id="f-votes">0</span><span class="m">this machine signs a checkpoint every 30 s</span></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">rules digest</div>
|
||||
<div class="box mono"><span id="n-digest">not printed yet</span><button class="btn tiny" data-copy="n-digest">Copy</button></div>
|
||||
<p class="help">Every node on the network shows the same fingerprint of the rules. A peer with another one is refused.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">next rule switch</div>
|
||||
<div class="line-text" id="n-switch">none planned</div>
|
||||
<p class="help" id="n-switch-help"></p>
|
||||
<div class="switch-list mono" id="n-switches"></div>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="dag" aria-hidden="true"></canvas>
|
||||
<div class="legend"><span><i class="sw ember"></i>block this machine found</span><span><i class="sw glow"></i>just accepted</span><span><i class="sw line"></i>one minute</span></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Activity</h3><div class="eyebrow">newest first</div></div>
|
||||
<div class="card-head"><h3>Activity</h3><div class="head-right"><span class="stats mono" id="d-stats"></span><button class="btn small ghost" id="btn-open-log">Open the log</button></div></div>
|
||||
<canvas id="dag" aria-hidden="true"></canvas>
|
||||
<div class="feed" id="d-events"><div class="empty">No events yet.</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Earnings -->
|
||||
<section class="page" id="page-earnings" data-page="earnings" hidden>
|
||||
<div class="card">
|
||||
<div class="money">
|
||||
<div class="money-row"><span class="v" id="e-earned">£0.00</span><span class="s" id="e-earned-sub">earned: nothing is bought or sold on devnet</span></div>
|
||||
<div class="money-row"><span class="v small" id="e-ign">0.0000 IGN</span><span class="s" id="e-ign-sub">from 0 proofs</span></div>
|
||||
<div class="money-row"><span class="v small" id="e-blocks">0 blocks</span><span class="s" id="e-blocks-sub">lifetime</span></div>
|
||||
<div class="money-row"><span class="v small" id="e-cost">£0.00 a day</span><span class="s" id="e-cost-sub">electricity</span><button class="btn tiny ghost" id="e-price-btn">Set the price</button></div>
|
||||
</div>
|
||||
<label class="switch"><input type="checkbox" id="s-devfee"><span class="track"></span><span class="sw-text" id="s-devfee-text">Dev fee: 1 block in 100 pays the miner software's author</span></label>
|
||||
<p class="help" id="r-devfee-line"></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Rewards address</h3><div class="eyebrow" id="r-source"></div></div>
|
||||
<div class="addr-big mono"><span id="r-address">not set</span><button class="btn small" data-copy="r-address">Copy</button></div>
|
||||
<p class="help" id="r-address-help">Every block this machine finds pays this address.</p>
|
||||
|
||||
<button class="disclose" id="addr-toggle" aria-expanded="false" aria-controls="addr-details"><span>Change address</span><span class="chev" aria-hidden="true"></span></button>
|
||||
<div class="details" id="addr-details" hidden>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input mono" id="s-address-input" placeholder="0x" spellcheck="false" autocomplete="off" aria-label="New rewards address">
|
||||
<button class="btn small" id="s-address-save">Change</button>
|
||||
</div>
|
||||
<div class="err" id="s-address-err" hidden></div>
|
||||
<div class="ask inline" id="ask-address" hidden>
|
||||
<span class="ask-text" id="ask-address-text"></span>
|
||||
<button class="btn small primary" id="ask-address-yes">Confirm</button>
|
||||
<button class="btn small ghost" id="ask-address-no">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="disclose" id="key-toggle" aria-expanded="false" aria-controls="key-details"><span id="key-toggle-text">Show my key</span><span class="chev" aria-hidden="true"></span></button>
|
||||
<div class="details" id="key-details" hidden>
|
||||
<p class="help" id="r-key-help"></p>
|
||||
<div class="ask inline" id="ask-key">
|
||||
<span class="ask-text">Show the private key on screen? Anyone who sees it can spend what the address holds.</span>
|
||||
<button class="btn small primary" id="s-reveal">Show</button>
|
||||
<button class="btn small ghost" id="ask-key-no">Cancel</button>
|
||||
</div>
|
||||
<div class="box mono key" id="s-key-box" hidden><span id="s-key"></span><button class="btn tiny" data-copy="s-key">Copy</button><button class="btn tiny ghost" id="s-hide">Hide</button></div>
|
||||
<p class="note mono small" id="r-key-file"></p>
|
||||
</div>
|
||||
|
||||
<div class="row wrap top-gap"><button class="btn small" id="r-wallet">Open the wallet</button><span class="note">Your balance is in the wallet.</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Prove -->
|
||||
<section class="page" id="page-prove" data-page="prove" hidden>
|
||||
<div class="card lead-card">
|
||||
<div class="card">
|
||||
<div class="lead-row">
|
||||
<div class="lead-text">
|
||||
<h3>Prove shards on this machine</h3>
|
||||
<p class="help">Every block on Igneum is turned into a short mathematical proof, in pieces called shards. The chain assigns shards to your keys; this machine proves them and is paid for each one. On by default on an NVIDIA card with 24 GB or more (a full shard needs 20.4 GB of GPU memory, measured); off on a Mac, whose CPU prover is slow.</p>
|
||||
<h3>Prove on this machine</h3>
|
||||
<p class="help">Every block is turned into a short proof. The chain hands pieces to your cards; each piece proven pays IGN.</p>
|
||||
</div>
|
||||
<label class="switch lg" title="Prove assigned shards"><input type="checkbox" id="s-prove" aria-label="Prove shards on this machine"><span class="track"></span></label>
|
||||
<label class="switch lg" title="Prove on this machine"><input type="checkbox" id="s-prove" aria-label="Prove on this machine"><span class="track"></span></label>
|
||||
</div>
|
||||
<p class="note" id="pv-note">Off. Switch it on and this machine proves the shards the chain assigns to its keys.</p>
|
||||
<ul class="tier-list" id="pv-tiers"></ul>
|
||||
<p class="line-text" id="pv-line"></p>
|
||||
<p class="note" id="pv-note"></p>
|
||||
<div class="row" id="pv-setup-row" hidden><button class="btn small primary" id="pv-setup">Set up</button><span class="note">About 20 minutes, once.</span></div>
|
||||
</div>
|
||||
<div class="strip four">
|
||||
<div class="cell"><div class="k">state</div><div class="v state" id="pv-state">off</div><div class="s" id="pv-state-sub">not proving</div></div>
|
||||
<div class="cell"><div class="k">assigned</div><div class="v" id="pv-assigned">0</div><div class="s">shards given to your keys</div></div>
|
||||
<div class="cell"><div class="k">proven</div><div class="v" id="pv-submitted">0</div><div class="s">proofs sent to the node</div></div>
|
||||
<div class="cell"><div class="k">paid</div><div class="v" id="pv-paid">0</div><div class="s" id="pv-paid-sub">shards paid out</div></div>
|
||||
</div>
|
||||
<p class="note" id="pv-seg-note" hidden></p>
|
||||
<div class="grid2">
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Verifier</h3><div class="eyebrow">the node's check</div></div>
|
||||
<div class="big-word" id="pv-verifier">not read yet</div>
|
||||
<p class="help" id="pv-verifier-help">Before a proof counts, the node checks it. A node without a verifier passes proofs along and never includes them.</p>
|
||||
<p class="note" id="pv-verifier-note" hidden></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Program</h3><div class="eyebrow">pinned guest</div></div>
|
||||
<button class="disclose" id="pv-toggle" aria-expanded="false" aria-controls="pv-details"><span>Details</span><span class="chev" aria-hidden="true"></span></button>
|
||||
<div class="details" id="pv-details" hidden>
|
||||
<div class="kv">
|
||||
<div><span class="k">verifier</span><span class="v" id="pv-verifier">not read yet</span><span class="m" id="pv-verifier-help">the node checks a proof before it counts</span></div>
|
||||
<div><span class="k">segments</span><span class="v" id="pv-seg">none yet</span><span class="m" id="pv-seg-note">whole segments this machine proved</span></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">shard program id</div>
|
||||
<div class="box mono"><span id="pv-program">not read yet</span><button class="btn tiny" data-copy="pv-program">Copy</button></div>
|
||||
|
|
@ -236,105 +318,63 @@
|
|||
<div class="field">
|
||||
<div class="k">aggregator id</div>
|
||||
<div class="box mono"><span id="pv-aggregator">not read yet</span><button class="btn tiny" data-copy="pv-aggregator">Copy</button></div>
|
||||
<p class="help">Every proof names the program that made it. Other nodes accept a proof only from these two ids.</p>
|
||||
</div>
|
||||
<p class="help">Every proof names the program that made it. Other nodes accept a proof only from these two ids.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Rewards -->
|
||||
<section class="page" id="page-rewards" data-page="rewards" hidden>
|
||||
<!-- Settings -->
|
||||
<section class="page" id="page-settings" data-page="settings" hidden>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Rewards address</h3><div class="eyebrow" id="r-source"></div></div>
|
||||
<div class="addr-big mono"><span id="r-address">not set</span><button class="btn small" data-copy="r-address">Copy</button></div>
|
||||
<p class="help" id="r-address-help">Every block this machine finds pays this address.</p>
|
||||
</div>
|
||||
<div class="strip three">
|
||||
<div class="cell"><div class="k">blocks found</div><div class="v" id="r-blocks">0</div><div class="s">lifetime, accepted by the node</div></div>
|
||||
<div class="cell"><div class="k">this run</div><div class="v" id="r-session">0</div><div class="s" id="r-session-sub">since the app started</div></div>
|
||||
<div class="cell"><div class="k">balance</div><div class="v dim" id="r-balance">--</div><div class="s">shown in the wallet, not here yet</div></div>
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<div class="card" id="r-key-card">
|
||||
<div class="card-head"><h3>Save your key</h3><div class="eyebrow ember">once</div></div>
|
||||
<p class="help" id="r-key-help">The key for this address was made on this machine and is stored in the app folder, readable by your user only. Keep a copy somewhere safe: anyone with the key can spend what the address holds.</p>
|
||||
<div class="row" id="r-key-row"><button class="btn small" id="s-reveal">Show my key</button><button class="btn small ghost" id="s-hide" hidden>Hide</button></div>
|
||||
<div class="box mono key" id="s-key-box" hidden><span id="s-key"></span><button class="btn tiny" data-copy="s-key">Copy</button></div>
|
||||
<p class="note mono small" id="r-key-file"></p>
|
||||
<div class="card-head"><h3>Tuning</h3><span class="eyebrow" id="s-tune-eyebrow">Ember Tune</span></div>
|
||||
<div class="goal-row">
|
||||
<div class="seg" id="goal-seg-2" role="radiogroup" aria-label="Tuning goal">
|
||||
<button class="seg-b" data-goal="efficiency" role="radio" aria-checked="false">Efficiency</button>
|
||||
<button class="seg-b" data-goal="balanced" role="radio" aria-checked="false">Balanced</button>
|
||||
<button class="seg-b" data-goal="rate" role="radio" aria-checked="false">Maximum</button>
|
||||
</div>
|
||||
<span class="goal-line" id="goal-line-2"></span>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Use another address</h3></div>
|
||||
<p class="help">Paste an EVM address you control. The miner restarts and pays the new address from the next block.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-sweep"><span class="track"></span><span class="sw-text"><b>Ember Tune</b> <span class="dim">Tunes every card for hashes per watt: once after install, then every 7 days, and after a driver or program change.</span></span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-power-control"><span class="track"></span><span class="sw-text"><b>Power control</b> <span class="dim">Lets the app set NVIDIA power and clock limits. Windows asks for administrator rights once. Off, NVIDIA cards are measured only.</span><span class="dim" id="s-power-note"></span></span></label>
|
||||
<label class="switch" id="s-climb-row" hidden><input type="checkbox" id="s-climb"><span class="track"></span><span class="sw-text"><b>Hill climb</b> <span class="dim">Searches around the best point instead of walking the ladders.</span></span></label>
|
||||
<div class="field">
|
||||
<div class="k">electricity price</div>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input mono" id="s-address-input" placeholder="0x" spellcheck="false" autocomplete="off" aria-label="New rewards address">
|
||||
<button class="btn small" id="s-address-save">Change</button>
|
||||
<input type="number" class="addr-input mono short" id="s-price" min="0" max="200" step="0.1" placeholder="28" aria-label="Electricity price in pence per kWh"><span class="note">pence per kWh. Every £ figure uses it.</span>
|
||||
<button class="btn small" id="s-price-save">Save</button>
|
||||
</div>
|
||||
<div class="err" id="s-address-err" hidden></div>
|
||||
<p class="note" id="r-devfee-line"></p>
|
||||
<div class="row"><button class="btn small ghost" id="r-wallet">Open the wallet page</button></div>
|
||||
</div>
|
||||
<p class="line-text" id="s-tune-schedule"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Node -->
|
||||
<section class="page" id="page-node" data-page="node" hidden>
|
||||
<div class="strip four">
|
||||
<div class="cell" id="n-state-cell"><div class="k">node</div><div class="v state" id="d-node">starting</div><div class="s" id="d-node-sub">opening the database</div></div>
|
||||
<div class="cell"><div class="k">height</div><div class="v" id="n-blocks">0</div><div class="s" id="n-blocks-sub">blocks this node holds</div></div>
|
||||
<div class="cell"><div class="k">peers</div><div class="v" id="n-peers">0</div><div class="s" id="n-peers-sub">other nodes it talks to</div></div>
|
||||
<div class="cell"><div class="k">version</div><div class="v small" id="n-version">--</div><div class="s" id="d-node-net">devnet v4</div></div>
|
||||
</div>
|
||||
<div class="clock-card" id="n-clock" hidden>
|
||||
<p class="clock-msg" id="n-clock-msg"></p>
|
||||
<div class="row"><button class="btn small primary" id="n-clock-sync">Sync clock</button><span class="note mono small" id="n-clock-result"></span></div>
|
||||
<p class="note" id="n-clock-hint"></p>
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Chain</h3><div class="eyebrow" id="n-reading">reading</div></div>
|
||||
<div class="kv">
|
||||
<div><span class="k">headers</span><span class="v mono" id="n-headers">0</span></div>
|
||||
<div><span class="k">daa score</span><span class="v mono" id="n-daa">0</span></div>
|
||||
<div><span class="k">difficulty</span><span class="v mono" id="n-diff">0</span></div>
|
||||
<div><span class="k">tips</span><span class="v mono" id="n-tips">0</span></div>
|
||||
<div><span class="k">blue score</span><span class="v mono" id="n-blue">0</span></div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>This machine</h3></div>
|
||||
<label class="switch"><input type="checkbox" id="s-login"><span class="track"></span><span class="sw-text"><b>Start at login</b> <span class="dim">Opens when you sign in and keeps mining in the background.</span></span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-jobs-allow"><span class="track"></span><span class="sw-text"><b>Allow remote jobs from Igneum</b> <span class="dim">Signed jobs (a benchmark, a script, logs to collect) run here once and report back.</span></span></label>
|
||||
<div class="row wrap indent"><span class="note" id="s-jobs-note"></span><button class="btn tiny ghost" id="s-jobs-check">Check now</button><button class="btn tiny ghost" id="s-jobs-history-btn" aria-expanded="false">History</button></div>
|
||||
<div class="details indent" id="s-jobs-history" hidden></div>
|
||||
<p class="note mono small indent" id="s-jobs-key" hidden></p>
|
||||
<div class="field">
|
||||
<div class="k">name</div>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input" id="s-name" placeholder="a name for this machine" maxlength="40" spellcheck="false" aria-label="Machine name">
|
||||
<button class="btn small" id="s-name-save">Rename</button>
|
||||
</div>
|
||||
<p class="help">Headers arrive before blocks. The DAA score counts blocks the whole network made; the difficulty is how hard the next one is to find.</p>
|
||||
<p class="help">A label for you only.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Consensus</h3><div class="eyebrow">the rules</div></div>
|
||||
<div class="field">
|
||||
<div class="k">digest</div>
|
||||
<div class="box mono"><span id="n-digest">not printed yet</span><button class="btn tiny" data-copy="n-digest">Copy</button></div>
|
||||
<p class="help">The fingerprint of the rules this node runs. Every node on the network shows the same one; a peer with another is refused.</p>
|
||||
<div class="field">
|
||||
<div class="k">appearance</div>
|
||||
<div class="seg" id="theme-seg" role="radiogroup" aria-label="Appearance">
|
||||
<button class="seg-b" data-theme="system" role="radio" aria-checked="true">System</button>
|
||||
<button class="seg-b" data-theme="light" role="radio" aria-checked="false">Light</button>
|
||||
<button class="seg-b" data-theme="dark" role="radio" aria-checked="false">Dark</button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">next switch</div>
|
||||
<div class="big-word" id="n-switch">none planned</div>
|
||||
<p class="help" id="n-switch-help">A switch is a planned rule change. The node applies it by itself when the chain reaches that height.</p>
|
||||
<div class="switch-list mono" id="n-switches"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Finality</h3><div class="eyebrow">miner-only</div></div>
|
||||
<div class="kv">
|
||||
<div><span class="k">last lock</span><span class="v mono" id="f-lock">none yet</span></div>
|
||||
<div><span class="k">age</span><span class="v mono" id="f-age">n/a</span></div>
|
||||
<div><span class="k">votes sent</span><span class="v mono" id="f-votes">0</span></div>
|
||||
</div>
|
||||
<p class="help" id="f-note">A lock is a point the miners have agreed can never be undone. This machine votes on one every 30 s.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Sync</h3><div class="eyebrow" id="n-sync-eyebrow"></div></div>
|
||||
<div class="big-word" id="n-sync-word">starting</div>
|
||||
<p class="help" id="n-note"></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Updates -->
|
||||
<section class="page" id="page-updates" data-page="updates" hidden>
|
||||
<div class="card lead-card">
|
||||
<div class="card">
|
||||
<div class="lead-row">
|
||||
<div class="lead-text">
|
||||
<h3 id="s-version">Igneum Miner</h3>
|
||||
|
|
@ -342,79 +382,39 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<button class="btn small primary" id="s-install" hidden>Install now</button>
|
||||
<button class="btn small" id="s-update">Check now</button>
|
||||
<button class="btn small" id="s-update">Check</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="switch"><input type="checkbox" id="s-auto-update"><span class="track"></span><span>Install updates by itself</span></label>
|
||||
<p class="help">Downloads in the background and installs at a quiet moment, never mid-program. Off: it downloads, then waits for Install now.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-auto-update"><span class="track"></span><span class="sw-text"><b>Install updates by itself</b> <span class="dim">Downloads in the background and installs at a quiet moment, never mid-program.</span></span></label>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="lead-row">
|
||||
<div class="lead-text">
|
||||
<h3>Remote jobs</h3>
|
||||
<p class="help">Igneum publishes signed jobs (a benchmark, a script, logs to collect) next to the update manifest. This machine runs each one once and reports back. Only jobs signed by Igneum's key run.</p>
|
||||
</div>
|
||||
<button class="btn small" id="s-jobs-check">Check now</button>
|
||||
</div>
|
||||
<p class="note" id="s-jobs-note"></p>
|
||||
<div class="job-history" id="s-jobs-history"></div>
|
||||
<p class="note mono small" id="s-jobs-key"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Settings -->
|
||||
<section class="page" id="page-settings" data-page="settings" hidden>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Graphics cards</h3><div class="eyebrow" id="s-cards-eyebrow"></div></div>
|
||||
<div class="set-cards" id="s-cards"><div class="empty">No card yet.</div></div>
|
||||
<label class="switch"><input type="checkbox" id="s-sweep"><span class="track"></span><span>Ember Tune: tune every card for hashes per watt</span></label>
|
||||
<p class="help">Once after install, then weekly and after a driver or program change: the power limit steps from 100% down to 50%, then the core clock from its maximum down to 60%, 75 s a step on the live program; the memory clock is never touched. The card keeps the point with the most hashes per watt within 1% of its top rate. A step with a rejected hash, a hot GPU or a dragged memory clock is reverted. A card whose model the fleet already knows starts at that point and confirms it in two steps. Every result goes back to the fleet without anything that identifies you. A cap you set by hand is left alone.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-power-control"><span class="track"></span><span>Power control: let the app set NVIDIA limits</span></label>
|
||||
<p class="help">Windows asks for administrator rights once; the NVIDIA cap and the tune need them. Off, the app never asks and NVIDIA cards measure only. AMD cards need no rights. <span id="s-power-note"></span></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>This machine</h3></div>
|
||||
<label class="switch"><input type="checkbox" id="s-login"><span class="track"></span><span>Start at login</span></label>
|
||||
<p class="help">The miner opens when you sign in and keeps mining in the background.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-jobs-allow"><span class="track"></span><span>Allow remote jobs from Igneum</span></label>
|
||||
<p class="help">Signed jobs from Igneum run on this machine and report back. Updates shows what ran.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-vote"><span class="track"></span><span>Vote on finality checkpoints</span></label>
|
||||
<p class="help">Your miner signs a checkpoint every 30 s. Votes are what lock the chain; leave it on.</p>
|
||||
<div class="field">
|
||||
<div class="k">name</div>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input" id="s-name" placeholder="a name for this machine" maxlength="40" spellcheck="false" aria-label="Machine name">
|
||||
<button class="btn small" id="s-name-save">Rename</button>
|
||||
</div>
|
||||
<p class="help">A label for you only. Keys come from the machine id <span class="mono" id="s-mid"></span>, never from the name.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Dev fee</h3></div>
|
||||
<label class="switch"><input type="checkbox" id="s-devfee"><span class="track"></span><span id="s-devfee-text">Dev fee 1% (1 block in 100)</span></label>
|
||||
<p class="help" id="s-devfee-note">One block in 100 is mined for the miner software's author, the same way every GPU miner takes a fee. The protocol itself takes nothing. This switch turns it off.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Logs</h3></div>
|
||||
<div class="row wrap">
|
||||
<button class="btn small" id="s-log-copy">Copy the log</button>
|
||||
<button class="btn small ghost" id="s-log-open">Show the log</button>
|
||||
<span class="note">The last lines the node and the miner wrote, for a support message.</span>
|
||||
</div>
|
||||
<p class="help">Copies the last lines the node and the miner wrote, for a support message. Show the log opens the drawer with every line.</p>
|
||||
<p class="note mono small" id="s-log-dir"></p>
|
||||
<p class="note mono small" id="s-node-dir"></p>
|
||||
</div>
|
||||
|
||||
<details class="card adv" id="s-advanced">
|
||||
<summary><h3>Advanced</h3><span class="eyebrow">devnet tools</span></summary>
|
||||
<label class="switch"><input type="checkbox" id="s-trust"><span class="track"></span><span>Trust proof records without verifying them</span></label>
|
||||
<p class="help" id="s-trust-note">Devnet only. When no verifier is found next to the engine, the node includes proof records it never checked. A found verifier always wins. Changing this restarts the node.</p>
|
||||
<div class="row"><button class="btn small ghost" id="s-live" hidden>Open the live devnet page</button></div>
|
||||
<summary><h3>Advanced</h3><span class="eyebrow">devnet</span></summary>
|
||||
<label class="switch"><input type="checkbox" id="s-vote"><span class="track"></span><span class="sw-text"><b>Vote on finality checkpoints</b> <span class="dim">Signs a checkpoint every 30 s. Votes lock the chain; leave it on.</span></span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-trust"><span class="track"></span><span class="sw-text"><b>Trust proof records without verifying them</b> <span class="dim">Devnet only. Without a verifier the node includes records it never checked. Changing this restarts the node.</span></span></label>
|
||||
<div class="ask inline" id="ask-trust" hidden>
|
||||
<span class="ask-text" id="ask-trust-text"></span>
|
||||
<button class="btn small primary" id="ask-trust-yes">Confirm</button>
|
||||
<button class="btn small ghost" id="ask-trust-no">Cancel</button>
|
||||
</div>
|
||||
<div class="row wrap"><button class="btn small ghost" id="s-live" hidden>Open the live devnet page</button><span class="note mono small" id="s-mid"></span></div>
|
||||
</details>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- the update card (app.js, UpdateCard): one update, centred; Later, Escape or the backdrop leaves the strip above -->
|
||||
<!-- the update card (app.js, UpdateCard) -->
|
||||
<div class="upd-wrap" id="upd" hidden>
|
||||
<div class="upd-card" id="upd-card" role="dialog" aria-modal="true" aria-labelledby="upd-name" aria-describedby="upd-line" tabindex="-1">
|
||||
<div class="upd-mark" id="upd-mark">
|
||||
|
|
@ -440,16 +440,15 @@
|
|||
<div class="eyebrow ember">shown once</div>
|
||||
<h2>Save your key</h2>
|
||||
<p class="sub">This key controls the rewards address. Keep a copy somewhere safe. Anyone with the key can spend what the address holds.</p>
|
||||
<div class="field">
|
||||
<div class="k">rewards address</div>
|
||||
<div class="box mono"><span id="key-address"></span><button class="btn tiny" data-copy="key-address">Copy</button></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">private key</div>
|
||||
<div class="box mono key"><span id="key-private"></span><button class="btn tiny" data-copy="key-private">Copy</button></div>
|
||||
</div>
|
||||
<p class="note">Stored at <span class="mono" id="key-file"></span>, readable by your user only. Rewards can show it again.</p>
|
||||
<p class="note">On devnet the vote keys are test keys derived from the miner's label. Mainnet vote keys will be random and stored like this wallet.</p>
|
||||
<div class="field">
|
||||
<div class="k">rewards address</div>
|
||||
<div class="box mono"><span id="key-address"></span><button class="btn tiny" data-copy="key-address">Copy</button></div>
|
||||
</div>
|
||||
<p class="note">Stored at <span class="mono" id="key-file"></span>, readable by your user only. Earnings can show it again.</p>
|
||||
<label class="check"><input type="checkbox" id="key-ack"><span>I have saved my key</span></label>
|
||||
<div class="cta">
|
||||
<button class="btn primary big" id="btn-key-done" disabled>Start mining</button>
|
||||
|
|
@ -457,8 +456,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- the log drawer: chips filter by source, search filters by text, the ruler and the jump controls move in time.
|
||||
The list is virtualised (fixed row height, only the visible rows are in the DOM) so 20,000 lines scroll smoothly. -->
|
||||
<!-- the log drawer (unchanged from miner-ui-2) -->
|
||||
<div class="drawer" id="drawer" aria-label="Logs">
|
||||
<div class="drawer-grip" id="drawer-grip" title="Drag to resize"><i></i></div>
|
||||
<div class="drawer-head">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ const src = readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'app.js')
|
|||
const mod = { exports: {} };
|
||||
new Function('module', src)(mod);
|
||||
const { model, point } = mod.exports.TuneLine;
|
||||
const View = mod.exports.View;
|
||||
const tuneNotice = mod.exports.tuneNotice;
|
||||
const NOW = 1_800_000_000;
|
||||
const card = over => ({ vendor: 'nvidia', sweep_state: 'idle', sweep_note: '', sweep_pct: 0, power_pct: 80, sweep_at: 0, tune_line: '', tune_source: '', tune_clock_mhz: 0, tune_control: true, pinned: false, ...over });
|
||||
|
||||
|
|
@ -42,3 +44,39 @@ test('running, stopped, idle and off', () => {
|
|||
assert.equal(model(card({ vendor: 'other' }), NOW).kind, 'off');
|
||||
assert.equal(point({ tune_clock_mhz: 0, sweep_pct: 0, power_pct: 0 }), '');
|
||||
});
|
||||
|
||||
test('a card never shows 0 MH/s without a reason word: tuning, held and tuned rows, the button, the strip', () => {
|
||||
const base = { key: 'nvidia:0:x', name: 'RTX 5090', vendor: 'nvidia', kind: 'discrete', enabled: true, state: 'tuning', hash_now: 123.4, power_w: 219.6, tune_step: 7, tune_steps: 9, tune_eta_s: 230, tune_plan: 'full', sweep_note: 'tuning: holding 2472 MHz · 100% · 41 s (step 7 of 9)', vram_mb: 32768 };
|
||||
const r = View.cardRow(base);
|
||||
assert.equal(r.word, 'tuning: step 7 of 9 · measuring 220 W');
|
||||
assert.equal(r.hash, '123', 'the live rate stays in the rate column');
|
||||
assert.equal(r.tone, 'on');
|
||||
const h = View.cardRow({ ...base, state: 'held', hash_now: 0, power_w: 0, message: 'held for a remote job: Ember Tune on PC 1', tune_steps: 0 });
|
||||
assert.equal(h.word, 'held for a remote job: Ember Tune on PC 1');
|
||||
assert.equal(h.hash, '', 'blank, never 0');
|
||||
const off = View.cardRow({ ...base, state: 'off', enabled: false, hash_now: 0 });
|
||||
assert.equal(off.word, 'off'); assert.equal(off.hash, '');
|
||||
// the big button during a tune
|
||||
const t = View.toggle({ cards: [base], state: 'mining', paused: false }, { synced: true }, {});
|
||||
assert.equal(t.label, 'Tuning'); assert.equal(t.disabled, true);
|
||||
assert.equal(t.sub, 'mining again in about 4 min · RTX 5090');
|
||||
const held = View.toggle({ cards: [{ ...base, state: 'held' }], state: 'waiting', paused: false }, { synced: true }, {});
|
||||
assert.equal(held.label, 'Held'); assert.equal(held.disabled, true);
|
||||
// the strip
|
||||
const n = tuneNotice([base]);
|
||||
assert.equal(n.kind, 'tuning');
|
||||
assert.equal(n.text, 'Tuning RTX 5090: the full tune, step 7 of 9. Mining again in about 4 min.');
|
||||
assert.equal(tuneNotice([{ ...base, state: 'mining' }]), null);
|
||||
// the finished row: TuneLine's tuned model (the table's "Tuned: ..." line with the point)
|
||||
const done = model({ ...base, state: 'mining', sweep_state: 'idle', tune_line: 'Tuned: 123.4 MH/s at 220 W (0.561 MH/W)', tune_source: 'full', tune_clock_mhz: 2470, sweep_pct: 100, sweep_at: NOW - 60, tune_control: true }, NOW);
|
||||
assert.equal(done.kind, 'tuned');
|
||||
assert.equal(done.text, 'Tuned: 123.4 MH/s at 220 W (0.561 MH/W)');
|
||||
assert.match(done.note, /^2470 MHz at 100%, full tune, 1 min ago/);
|
||||
assert.equal(View.tuneEta(30), 'under a minute');
|
||||
assert.equal(View.tuneEta(700), 'about 12 min');
|
||||
});
|
||||
|
||||
test('Ember 2: £ a day from watts and pence per kWh', () => {
|
||||
assert.equal(mod.exports.TuneLine.pounds(310, 28.5), '£2.12');
|
||||
assert.equal(mod.exports.TuneLine.pounds(102.7, 28.5), '£0.70');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ test('release notes: sentences, three lines under 70 characters, the rest behind
|
|||
|
||||
test('what the card says: available, downloading with the ring, ready, installing, failed, manual, waiting', () => {
|
||||
const a = model(upd({ status: 'available', downloaded: false, ready: false, progress: 0 }), {});
|
||||
assert.equal(a.name, 'Igneum Ember 0.3.7'); assert.equal(a.line, 'is available.'); assert.equal(a.stage, 'available');
|
||||
assert.equal(a.name, 'Igneum Miner 0.3.7'); assert.equal(a.line, 'is available.'); assert.equal(a.stage, 'available');
|
||||
assert.equal(a.key, 'update:0.3.7:pending'); assert.equal(a.ring, 'none'); assert.equal(a.size, '21 MB');
|
||||
assert.deepEqual(a.actions.map((x) => x.label), ['Install now', 'Later']); assert.equal(a.dismissable, true);
|
||||
const d = model(upd({ status: 'downloading', downloaded: false, ready: false, progress: 0.43 }), {});
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ const V = mod.exports.View;
|
|||
|
||||
const card = (over) => ({ key: 'nvidia:0:RTX 5090', name: 'NVIDIA GeForce RTX 5090', vendor: 'nvidia', kind: 'discrete', worker: 'CUDA', vram_mb: 32768, enabled: true, state: 'mining', hash_now: 124.3, hash_avg: 120, accepted: 3, rejected: 0, identities: 8, ids: [], prepared: true, restart_in_s: 0, message: '', reason: '', power_w: 410.2, power_limit_w: 460, power_default_w: 575, power_pct: 80, power_applied: true, temp_gpu: 61, temp_mem: 72, telemetry_at: 1, ...over });
|
||||
|
||||
test('the six sections and their order', () => {
|
||||
assert.deepEqual(V.PAGES.map((p) => p.id), ['mine', 'prove', 'rewards', 'node', 'updates', 'settings']);
|
||||
assert.equal(V.page('node').title, 'Node');
|
||||
test('the four sections and their order (miner-ui-3)', () => {
|
||||
assert.deepEqual(V.PAGES.map((p) => p.id), ['mine', 'earnings', 'prove', 'settings']);
|
||||
assert.equal(V.page('earnings').title, 'Earnings');
|
||||
assert.equal(V.page('nonsense').id, 'mine');
|
||||
});
|
||||
|
||||
|
|
@ -129,9 +129,9 @@ test('the prove words follow the switch, the setup, the node and the status', ()
|
|||
assert.equal(V.verifierWords({}).word, 'not read yet');
|
||||
});
|
||||
|
||||
test('the dev-fee lines name the share and where Settings turns it off', () => {
|
||||
test('the dev-fee lines name the share and the switch that turns it off', () => {
|
||||
const s = (on, fee) => ({ settings: { dev_fee: on }, mining: { fee_total: fee }, dev_fee: { on, percent: on ? 1 : 0, address: '0x1234567890abcdef1234567890abcdef12345678', line: '' } });
|
||||
assert.equal(V.devFeeLine(s(true, 12)), 'One block in 100 pays the miner software’s dev fee (12 so far). Settings turns it off.');
|
||||
assert.equal(V.devFeeLine(s(true, 12)), 'One block in 100 pays the miner software’s dev fee (12 so far). The switch above turns it off.');
|
||||
assert.equal(V.devFeeLine(s(false, 0)), 'The dev fee is off. Every block pays this address.');
|
||||
assert.equal(V.devFeeText(s(true, 0)), 'Dev fee 1% (1 block in 100) to 0x123456…5678');
|
||||
assert.match(V.devFeeText(s(false, 0)), /^Dev fee off/);
|
||||
|
|
@ -139,7 +139,7 @@ test('the dev-fee lines name the share and where Settings turns it off', () => {
|
|||
|
||||
test('the remote-jobs line and the helpers', () => {
|
||||
const title = (j) => j.title || j.kind;
|
||||
assert.equal(V.jobsNote({ allowed: false }, 1000, title), 'Off: nothing runs here until Settings allows remote jobs.');
|
||||
assert.equal(V.jobsNote({ allowed: false }, 1000, title), 'Off: nothing runs here until the switch above allows remote jobs.');
|
||||
assert.equal(V.jobsNote({ allowed: true, url_set: false }, 1000, title), 'No jobs address in this build.');
|
||||
assert.equal(V.jobsNote({ allowed: true, url_set: true, active: true, id: 'job-3', kind: 'build', title: 'build' }, 1000, title), 'Running build (job-3).');
|
||||
assert.equal(V.jobsNote({ allowed: true, url_set: true, checked_at: 940, queued: 2 }, 1000, title), 'Nothing running; checked 1 min ago; 2 queued.');
|
||||
|
|
@ -198,3 +198,120 @@ test('hot-plug (src/hotplug.rs): a removed card and a faulty card are shown as s
|
|||
const t2 = V.toggle({ state: 'mining', paused: false, cards: [card(), gone] }, { synced: true }, {});
|
||||
assert.equal(t2.sub, 'mining on 1 of 1 card');
|
||||
});
|
||||
|
||||
// ---- miner-ui-3: the row as the hero object, the money, the tune line, the prove tier, the node line, the asks ----
|
||||
const NOW = 1_800_000_000;
|
||||
const settings = (over) => ({ settings: { sweep: true, tuning_off: false, tune_period_s: 604800, power_control: true, ...over }, now: NOW });
|
||||
|
||||
test('the row word: every state carries a reason, and a paused miner says paused on a card that is on', () => {
|
||||
const m = { paused: true, state: 'paused' };
|
||||
assert.equal(V.rowWord(V.cardRow(card({ state: 'off', hash_now: 0 })), m), 'paused');
|
||||
assert.equal(V.rowWord(V.cardRow(card({ state: 'waiting', hash_now: 0 })), m), 'paused');
|
||||
assert.equal(V.rowWord(V.cardRow(card()), { paused: false, state: 'mining' }), 'mining');
|
||||
assert.equal(V.rowWord(V.cardRow(card({ enabled: false, state: 'off' })), m), 'off');
|
||||
assert.equal(V.cardRow(card({ state: 'held', hash_now: 0, message: 'held for a remote job: shard benchmark' })).word, 'held for a remote job: shard benchmark');
|
||||
assert.equal(V.cardRow(card({ state: 'tuning', tune_step: 3, tune_steps: 9, power_w: 300 })).word, 'tuning: step 3 of 9 · measuring 300 W');
|
||||
assert.equal(V.cardRow(card({ state: 'starting', hash_now: 0 })).word, 'starting');
|
||||
assert.equal(V.cardRow(card({ state: 'failed', hash_now: 0 })).tone, 'bad');
|
||||
});
|
||||
|
||||
test('money: £ a day from watts at a price, nothing without one, the cells leave out what is not reported', () => {
|
||||
assert.equal(V.poundsPerDay(310, 28.5).toFixed(4), '2.1204');
|
||||
assert.equal(V.money(V.poundsPerDay(310, 28.5)), '£2.12');
|
||||
assert.equal(V.poundsPerDay(310, 0), null);
|
||||
assert.equal(V.poundsPerDay(0, 28), null);
|
||||
assert.equal(V.money(123.4), '£123');
|
||||
assert.equal(V.money(null), '');
|
||||
const c = V.cells(card(), 28);
|
||||
assert.equal(c.hash, '124'); assert.equal(c.power, '410 W'); assert.equal(c.money, '£2.76'); assert.equal(c.temp, '61 °C'); assert.equal(c.needsPrice, false); assert.equal(c.noReading, '');
|
||||
assert.equal(c.eff, '0.30 MH/W');
|
||||
const noPrice = V.cells(card(), 0);
|
||||
assert.equal(noPrice.money, ''); assert.equal(noPrice.needsPrice, true);
|
||||
const apple = V.cells(card({ vendor: 'apple', kind: 'apple', power_w: 0, temp_gpu: 0, temp_mem: 0, eff_mhw: 0 }), 28);
|
||||
assert.equal(apple.power, ''); assert.equal(apple.money, ''); assert.equal(apple.temp, ''); assert.equal(apple.needsPrice, false);
|
||||
assert.equal(apple.noReading, 'No power or temperature reading on Apple silicon');
|
||||
assert.equal(V.cells(card({ kind: 'integrated', vendor: 'other', power_w: 0, temp_gpu: 0, eff_mhw: 0 }), 28).noReading, 'No power or temperature reading on an integrated GPU');
|
||||
assert.equal(V.wattsTotal([card(), card({ key: 'b', power_w: 100 }), card({ key: 'c', power_w: 50, removed_at: 5 })]), 510.2);
|
||||
assert.equal(V.effText(V.effOf(card({ eff_mhw: 0.422 }))), '0.42 MH/W');
|
||||
assert.equal(V.effText(V.effOf(card({ eff_mhw: 0, power_w: 0 }))), '');
|
||||
});
|
||||
|
||||
test('the tune line: never run, running with a step and a bar, tuned with the point and the next check, the prior, measured only, pinned, stopped, fleet pause, no control', () => {
|
||||
const nv = (over) => card({ sweep_state: 'idle', sweep_note: '', sweep_pct: 0, sweep_at: 0, tune_line: '', tune_source: '', tune_clock_mhz: 0, tune_control: true, pinned: false, tune_step: 0, tune_steps: 0, tune_eta_s: 0, ...over });
|
||||
const idle = V.tuneWords(nv(), settings(), NOW);
|
||||
assert.equal(idle.kind, 'idle'); assert.equal(idle.text, 'Not tuned yet: starts after 2 min of steady mining'); assert.equal(idle.button, 'tune'); assert.equal(idle.progress, -1);
|
||||
assert.equal(V.tuneWords(nv(), settings({ sweep: false }), NOW).text, 'Not tuned yet: Ember Tune is off in Settings');
|
||||
const run = V.tuneWords(nv({ state: 'tuning', tune_step: 7, tune_steps: 9, tune_eta_s: 230 }), settings(), NOW);
|
||||
assert.equal(run.kind, 'running'); assert.equal(run.text, 'Tuning: step 7 of 9 · about 4 min left'); assert.equal(run.button, 'stop'); assert.equal(run.progress.toFixed(2), '0.67');
|
||||
const runNoStep = V.tuneWords(nv({ sweep_state: 'running', sweep_note: 'tuning: waits for 120 s of steady mining' }), settings(), NOW);
|
||||
assert.equal(runNoStep.text, 'Tuning: waits for 120 s of steady mining');
|
||||
const tuned = V.tuneWords(nv({ tune_line: 'Tuned: 122.3 MH/s at 290 W (0.422 MH/W)', tune_source: 'full', tune_clock_mhz: 2470, sweep_pct: 100, sweep_at: NOW - 7200 }), settings(), NOW);
|
||||
assert.equal(tuned.kind, 'tuned'); assert.equal(tuned.note, '122.3 MH/s at 290 W (0.422 MH/W)'); assert.equal(tuned.button, 'tune');
|
||||
assert.match(tuned.text, /^Tuned 2 h ago · 2470 MHz at 100% · next check /);
|
||||
const prior = V.tuneWords(nv({ tune_line: 'Tuned: 122.3 MH/s at 290 W (0.422 MH/W)', tune_source: 'confirm', tune_clock_mhz: 2470, sweep_pct: 100, sweep_at: NOW - 120 }), settings(), NOW);
|
||||
assert.match(prior.text, /^Tuned 2 min ago from the fleet prior, confirmed · 2470 MHz at 100%/);
|
||||
const measured = V.tuneWords(nv({ tune_line: 'Tuned: 26.7 MH/s at 38 W (0.703 MH/W)', tune_source: 'baseline', tune_control: false, sweep_at: NOW - 600, sweep_note: 'measure only on Apple silicon: the system sets the clocks and the power; no control exposed' }), settings(), NOW);
|
||||
assert.equal(measured.kind, 'measured'); assert.equal(measured.text, 'Measured 26.7 MH/s at 38 W (0.703 MH/W) as it runs, 10 min ago');
|
||||
assert.equal(measured.note, 'Measured only on Apple silicon: the system sets the clocks and the power; no control exposed');
|
||||
const off = V.tuneWords(nv({ tune_control: false, sweep_note: 'measure only until Power control is on in Settings (Windows asks for administrator rights once)' }), settings({ power_control: false }), NOW);
|
||||
assert.equal(off.kind, 'idle'); assert.equal(off.note, 'Measured only until Power control is on in Settings (Windows asks for administrator rights once)');
|
||||
const pinned = V.tuneWords(nv({ tune_line: 'Tuned: 120 MH/s at 300 W (0.400 MH/W)', tune_source: 'full', pinned: true, power_pct: 80, sweep_at: NOW - 60 }), settings(), NOW);
|
||||
assert.match(pinned.text, /^Your cap stays pinned at 80% · Tuned 1 min ago/);
|
||||
const stopped = V.tuneWords(nv({ sweep_note: 'tuning stopped: a remote job took the GPU' }), settings(), NOW);
|
||||
assert.equal(stopped.kind, 'stopped'); assert.equal(stopped.text, 'Tuning stopped: a remote job took the GPU');
|
||||
const paused = V.tuneWords(nv(), settings({ tuning_off: true, tuning_note: 'tuning paused fleet-wide by the signed manifest' }), NOW);
|
||||
assert.equal(paused.kind, 'paused'); assert.equal(paused.button, '');
|
||||
assert.equal(V.tuneWords(card({ vendor: 'other', kind: 'integrated', sweep_supported: false }), settings(), NOW).kind, 'none');
|
||||
assert.equal(V.tunable(card({ vendor: 'apple', kind: 'apple' })), true);
|
||||
assert.equal(V.tunable(card({ removed_at: 5 })), false);
|
||||
});
|
||||
|
||||
test('the next check is a weekday inside a week, a date beyond it, due now when overdue', () => {
|
||||
assert.equal(V.nextCheck(0, 604800, NOW), '');
|
||||
assert.equal(V.nextCheck(NOW - 700000, 604800, NOW), 'next check due now');
|
||||
assert.equal(V.nextCheck(NOW - 604800 + 3600, 604800, NOW), 'next check within a day');
|
||||
const inThree = V.nextCheck(NOW - 604800 + 3 * 86400, 604800, NOW);
|
||||
assert.match(inThree, /^next check (Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)$/);
|
||||
assert.match(V.nextCheck(NOW, 604800 * 3, NOW), /^next check \d{1,2} [A-Z][a-z]{2}$/);
|
||||
assert.equal(V.nextCheck(NOW - 100, 0, NOW), V.nextCheck(NOW - 100, 604800, NOW), 'no period means seven days');
|
||||
});
|
||||
|
||||
test('the Tuning card schedule and the goal consequence', () => {
|
||||
const nv = (over) => card({ sweep_at: 0, tune_steps: 0, ...over });
|
||||
assert.equal(V.schedule([nv()], settings(), NOW), 'Not tuned yet: the first tune starts 2 min into steady mining.');
|
||||
assert.equal(V.schedule([nv()], settings({ sweep: false }), NOW), 'Ember Tune is off. Tune and Tune all still work by hand.');
|
||||
assert.equal(V.schedule([nv({ state: 'tuning', tune_step: 2, tune_steps: 9, tune_eta_s: 500 })], settings(), NOW), 'Tuning now: NVIDIA GeForce RTX 5090, step 2 of 9, mining again in about 8 min.');
|
||||
assert.match(V.schedule([nv({ sweep_at: NOW - 7200 }), nv({ key: 'b', name: 'B', sweep_at: NOW - 60 })], settings(), NOW), /^Last tune 1 min ago \(B\) · next check /);
|
||||
assert.equal(V.schedule([], settings(), NOW), 'No card here can be tuned.');
|
||||
assert.equal(V.schedule([nv()], settings({ tuning_off: true, tuning_note: 'tuning paused fleet-wide by the signed manifest' }), NOW), 'tuning paused fleet-wide by the signed manifest');
|
||||
const g = V.goalWords('efficiency', [card({ sweep_watts: 290, power_w: 410 })], 28);
|
||||
assert.equal(g.watts, 290); assert.equal(g.consequence, 'about £1.95 a day at 290 W');
|
||||
assert.equal(V.goalWords('rate', [card({ power_default_w: 575 })], 28).watts, 575);
|
||||
assert.equal(V.goalWords('balanced', [card()], 28).watts, 410);
|
||||
assert.equal(V.goalWords('nonsense', [card()], 0).goal, 'balanced');
|
||||
assert.equal(V.goalWords('balanced', [card()], 0).consequence, 'about 410 W');
|
||||
});
|
||||
|
||||
test('the prove tier sentence per card, and the counts in one line', () => {
|
||||
assert.equal(V.proveTier(card()), 'NVIDIA GeForce RTX 5090: proves while it mines (32 GB; a full shard needs 20.4 GB).');
|
||||
assert.equal(V.proveTier(card({ name: 'NVIDIA GeForce RTX 4070', vram_mb: 12288 })), 'NVIDIA GeForce RTX 4070: cannot prove a full shard (12 GB; a full shard needs 20.4 GB).');
|
||||
assert.equal(V.proveTier(card({ name: 'Apple M5 Max', vendor: 'apple' })), 'Apple M5 Max: proves on the CPU, slowly.');
|
||||
assert.equal(V.proveTier(card({ name: 'AMD Radeon RX 9070 XT', vendor: 'amd', vram_mb: 16384 })), 'AMD Radeon RX 9070 XT: cannot prove yet (the prover is CUDA only).');
|
||||
assert.equal(V.proveLine({ assigned: 0, submitted: 0, paid: 0, paid_wei: '0' }, false, true), 'Off · not proving · 0 assigned · 0 proven · 0 paid · 0.0000 IGN');
|
||||
assert.equal(V.proveLine({ status: 'idle', assigned: 3, submitted: 2, paid: 1, paid_wei: '1500000000000000000', available: true, enabled: true }, true, true), 'Idle · nothing assigned · 3 assigned · 2 proven · 1 paid · 1.5000 IGN');
|
||||
});
|
||||
|
||||
test('the node line, the activity line, the asks', () => {
|
||||
const n = { state: 'synced', blocks: 109859, headers: 109859, peers: 6, daa: 198000, last_reading_age_s: 5, message: '' };
|
||||
const l = V.nodeLine(n, { severity: 'none' }, '');
|
||||
assert.equal(l.text, 'Node synced · 6 peers · 109,859 blocks'); assert.equal(l.tone, 'ok'); assert.match(l.sub, /every block/);
|
||||
assert.equal(V.nodeLine({ ...n, peers: 1, blocks: 0, state: 'starting' }, { severity: 'none' }, '').text, 'Node starting · 1 peer');
|
||||
assert.equal(V.nodeLine(n, { severity: 'block', skew_s: -412 }, '').tone, 'bad');
|
||||
assert.equal(V.eventLine('consensus parameters from the signed manifest: {"difficulty_v2_activation_daa":33000}'), 'consensus parameters from the signed manifest (details in the log)');
|
||||
assert.equal(V.eventLine('node synced: 151512 blocks, 6 peer(s)'), 'node synced: 151512 blocks, 6 peer(s)');
|
||||
assert.equal(V.eventLine('a '.repeat(100), 40).length <= 41, true);
|
||||
assert.equal(V.addressAsk('0x1234567890abcdef1234567890abcdef12345678'), 'Pay 0x1234…5678 from the next block? The miner restarts.');
|
||||
assert.match(V.trustAsk(true), /^Include proof records the node never checked\? Devnet only; the node restarts\.$/);
|
||||
assert.match(V.trustAsk(false), /^Verify proof records again\?/);
|
||||
assert.equal(V.ago(30), 'just now'); assert.equal(V.ago(7200), '2 h ago'); assert.equal(V.ago(200000), '2 d ago');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ static void applyState(const std::string& j) {
|
|||
// Runs cmd /c <line> as administrator, waits, and answers on the engine's stdin.
|
||||
static void runElevated(std::wstring line) {
|
||||
std::thread([line] {
|
||||
std::wstring params = L"/c " + line;
|
||||
// cmd strips the first and last quote of a line that starts with one and holds more than two (two cards, or a
|
||||
// cap plus a script): one outer pair around the whole line is what it may strip (PC 1, 6 October 2026)
|
||||
std::wstring params = L"/c \"" + line + L"\"";
|
||||
wchar_t sysdir[MAX_PATH];
|
||||
GetSystemDirectoryW(sysdir, MAX_PATH);
|
||||
std::wstring cmdExe = std::wstring(sysdir) + L"\\cmd.exe"; // the absolute path, never a bare name (R4.3.3)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
// packaging/windows/Igneum-Miner.iss when the app version moves. Include guards, not #pragma once: rc.exe reads it too.
|
||||
#ifndef IGNEUM_HOST_VERSION_H
|
||||
#define IGNEUM_HOST_VERSION_H
|
||||
#define IGNEUM_HOST_VERSION_STR "0.3.13"
|
||||
#define IGNEUM_HOST_VERSION_RC 0,3,13,0
|
||||
#define IGNEUM_HOST_VERSION_STR "0.3.14"
|
||||
#define IGNEUM_HOST_VERSION_RC 0,3,14,0
|
||||
#endif
|
||||
|
|
|
|||
84
docs/analysis/51-percent.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# What a 51 percent attacker can and cannot do on Igneum
|
||||
|
||||
6 October 2026. For the litepaper and the ledger. Written for a reader who maintains Monero or Kaspa and does not take a finality claim on trust. Every number names its model or its measurement; "approximate" marks the rest. Models and runs: `sim/horizon/consensus-security/` (`ghostdag_sim.py`, `finality_horizon.py`, `cost_model.py`, `signalling.py`), the finality simulator `sim/finality_v2.py` with `sim/results_v2.md`, the specification `docs/spec/02-consensus.md` and `03-finality.md`. The long form is `docs/analysis/horizon/consensus-security.md`.
|
||||
|
||||
Price basis: USD 11.7 per GH/s-hour, measured on rented pods on 6 October 2026 (`docs/bench-log.md`, "Rental cost of hash": 1,748 MH/s for USD 20.44 an hour; the market supplied no more than about 2 GH/s that evening, so every figure above that is a list-price extrapolation). An attacker holding share A of the total hash rents A/(1 - A) times the honest network N.
|
||||
|
||||
## 1. What 51 percent buys, and at what price
|
||||
|
||||
Igneum orders blocks by GHOSTDAG (a rusty-kaspa fork, k = 18 at 1 block/s) and locks a checkpoint every 30 blue blocks when two thirds of the trailing 30 days of blue blocks, per vote key, have signed it (spec 03, Q3). The lock lands 63 to 93 s after the checkpoint block (determined 60 blue score later, certified about 3 s after; `sim/results_v2.md` A).
|
||||
|
||||
A majority of hash buys the ordering race up to that lock. The DAG simulator (`ghostdag_sim.py`, GHOSTDAG as `vendor/igneum-node/consensus/src/processes/ghostdag/protocol.rs` runs it, 20 seeds per cell, one-way delay 0.67 s = the cloud devnet's p99 propagation) gives, at 1 block/s:
|
||||
|
||||
| attacker share | hold 30 s | hold 60 s | hold 90 s | hold 120 s |
|
||||
|---|---|---|---|---|
|
||||
| 20% | wins 10%, reorg median 0 / max 18 | 0%, 0 / 1 | 0%, 0 / 1 | 0%, 0 / 1 |
|
||||
| 34% | 40%, 0 / 18 | 40%, 0 / 36 | 5%, 0 / 39 | 10%, 0 / 52 |
|
||||
| 51% | 85%, 10 / 15 | 85%, 20 / 28 | 80%, 32 / 44 | 80%, 42 / 53 |
|
||||
| 67% | 100%, 8 / 13 | 100%, 16 / 23 | 100%, 26 / 31 | 100%, 34 / 41 |
|
||||
|
||||
"wins" = the private chain became the honest selected chain on release; "reorg" = chain blocks removed. The arithmetic: a private chain merges honest blocks as blue until its first block has k in its anticone, then every later honest block is red in it; it wins when its R blocks plus k exceed the honest N_h, always at or above 50 percent, below it only for holds under k A / ((1 - 2A) lambda) seconds (6 s at 20%, 56 s at 34%). Withholding longer than the lock latency is useless: the first checkpoint inside the hold locks at most 93 s after it is mined, and a chain missing a certified checkpoint is not a fork-choice candidate (spec 03 F1). Bound: the lock latency, 90 to 120 s. Price of the 90-s race at 51 percent: USD 0.30 at 1 GH/s, USD 304 at 1 TH/s (`cost_model.py`). What it earns: a deposit credited before its lock, which the four-state rule forbids (ledger P17: a wallet shows included, executed, proven, finalised and credits on the last).
|
||||
|
||||
Repeated 60-s withholding at 51 percent turns 26 to 28 percent of honest blocks red; a red block's subsidy goes to its merger (spec 02 2.5), so the majority takes about a quarter of honest income and lifts its weight share to about 56 percent (approximate), reorganising the chain every minute in public. It does not reach two thirds.
|
||||
|
||||
The proving pool is the one place 51 percent earns more than it spends today. Consensus checks a proof record's signature and its statement against the node's own execution, and not the proof (spec 07 7.7 item 4, 7.8 item 8; ledger P21). A producer who writes the correct statement, random proof bytes and its own payout address into its own block is paid the shard; at 51 percent of blocks that is at least 51 percent of the 20 percent pool (11,636 IGN an hour at full subsidy) and, since its fake rides its next block while an honest proof takes 9 to 11 s on a 5090, most of the shards outside the 10-s exclusive window. Proof verification in consensus closes it; it is the first item in section 5.
|
||||
|
||||
## 2. What it cannot do, and why
|
||||
|
||||
| it cannot | because | measured |
|
||||
|---|---|---|
|
||||
| Reverse a certified checkpoint | a candidate tip must pass through every certified checkpoint (F1); two certificates at one index need two thirds of total weight each, 4/3 in all, so a conflicting pair needs an equivocator holding a third of 30 days of blocks (spec 03 3.11.2) | 0 conflicting locks under 1/3 in every partition and eclipse seed; conflicts from 34% (`sim/results_v2.md` H, I, L3, M5; `finality_horizon.py` P and E) |
|
||||
| Reach two thirds of weight with 51 percent of blocks | weight is blue blocks over a flat 30-day window: share = (t/30) A, ceiling A; 51% holds 51% on day 30 and never more while honest miners mine | the formula holds to 0.1 day (`results_v2.md` B, `finality_horizon.py` R); red-flooding lifts it to about 56% (approximate) |
|
||||
| Buy weight faster than mining it | a pulsed rental buys 0.26 blocks per hash under the controller (`sim/difficulty/attacks` scenario 2); a bought key is worth its blocks and decays as the window slides, share = A (1 - t/30) + r t/30 (3.11.5) | `results_v2.md` K, `finality_horizon.py` K: keys worth 51% hold the veto to day 24 and are worth 30% on day 30 |
|
||||
| Forge state | every full node executes natively and ignores a record whose statement differs from its own execution (the veto, spec 07 7.2 item 5); a soundness bug is a light-client problem (P7) | the exec-attacks suite, 96 of 97 checks on the shipped node (`docs/review/redteam-2026-10-04.md` row 28) |
|
||||
| Change a rule | code activates when 95% of a day's blue blocks signal it, with a floor height as backstop (P2, `docs/plans/counter-asic-3-node.md` section 6); the signal has 0.07 points of noise over 86,400 blocks, so 94.9% never flips and 95.1% flips on day one | `signalling.py`; the fast-time gate's three cases and its failed case |
|
||||
| Grind the hourly program | the epoch seed is a 10-minute class-group VDF of a checkpoint block fixed 20 minutes before the epoch; withholding a block to pick a program has expected gain 0 against a 300x evaluator margin (spec 04 4.1, 4.6) | `proto-vdf` Monte Carlo over 2,000,000 epochs |
|
||||
| Stretch the clocks | a header is at most 10 s ahead of the clock and 10 s behind its parent; a sanitised clock pays a forgery back | a 50% forger drifts the rate +0.4 to +1.1% (M23 fixed, `sim/difficulty/attacks/README.md`) |
|
||||
|
||||
## 3. The table: capability against share, time, cost and earnings
|
||||
|
||||
| capability | share | time | rent at 1 GH/s | at 100 GH/s | at 1 TH/s | subsidy it earns meanwhile (IGN) | net |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Reorder the last k blocks | any | 20 s | USD 0 | 2 | 24 | 0 | a loss |
|
||||
| Win the lock-latency race (90 s) | 45 to 51% | 90 s | USD 0.3 | 30 | 304 | 1k | nothing credited under a lock |
|
||||
| Reach the veto (1/3 of weight): pause finality at will | 51% | 20 days | USD 6k | 585k | 5.8M | 22M | the attacker earns 51% of it back |
|
||||
| the same | 90% | 11.1 days | USD 28k | 2.8M | 28M | 22M | |
|
||||
| Lock alone (2/3 of weight): certify any chain forward | 67% | 30 days | USD 17k | 1.7M | 17M | 44M | 33% of the period's subsidy at equilibrium |
|
||||
| the same | 90% | 22.2 days | USD 56k | 5.6M | 56M | 44M | |
|
||||
| Hold a pause once the veto is held | 1/3 | for ever | 0 marginal | 0 | 0 | keeps earning | free |
|
||||
| 12-h double spend during a pause or the first 30 days | 51% | 12 h | USD 146 | 15k | 146k | 559k | the deposit |
|
||||
| Orphan an hour of honest blocks beyond merge depth (pause only) | 51% | 1.5 h | USD 18 | 2k | 18k | 70k | the honest hour's subsidy, lost to all |
|
||||
| Private DAG heavier over the window (cold-start node, no certificate) | 51% | 30 days | USD 9k | 877k | 8.8M | 34M | one fresh node misled |
|
||||
| Capture the proving pool with fake records | any producer | continuous | 0 extra | 0 | 0 | up to 20% of emission | the one positive line |
|
||||
| Block a rule change | 6% | per day until the floor | USD 18 | 1.8k | 18k | 6% of subsidy | about zero |
|
||||
| Pause finality by taking the two hands down (tonight's topology) | 0 hash | | a DoS | | | 0 | free |
|
||||
|
||||
At the rental-market equilibrium, where hash joins until rent equals subsidy (39, 156 and 780 GH/s at IGN prices of USD 0.005, 0.02 and 0.10, the three inputs of `docs/analysis/security-budget.md`, not predictions), the veto nets about 48 percent of 20 days of the chain's subsidy and locking alone about 33 percent of 30 days (`cost_model.py` section 3).
|
||||
|
||||
## 4. The residual risks, plainly
|
||||
|
||||
1. **The pause.** Finality pauses whenever less than two thirds of 30-day weight is connected and signing, and the chain runs on proof of work with a 12-hour depth meanwhile (spec 03 3.7 item 2, 3.9). A third of weight holds the pause for nothing once it has it (`finality_horizon.py` S: 0 locks for the whole silence at 34 to 90 percent, 0 conflicts). During a pause a 51 percent miner is a 51 percent miner on any proof-of-work chain, with the 12-hour depth and USD 146 at 1 GH/s to buy it. What we are building against it is in section 5.
|
||||
|
||||
**The departure case** (not an attack, the same pause): tonight on the live devnet 20 keys holding 42.7 percent of the frozen weight table stopped mining within three minutes (a rehearsal job), the signing weight fell to 53.1 percent at checkpoint 6843 and finality paused at 18:39:40Z; rule v2 would have locked again after 35 minutes as the departed blocks aged out of the sliding table, and the frozen table (Q5, the live rule) holds the pause for one window, 2 hours there and 30 days on mainnet; locks had formed with the hand nodes down, so it was weight, not topology (`docs/analysis/horizon/finality-and-weight.md` 3.1 and 4.1; `finality_horizon.py` C: 51 percent leaving pauses 10.5 days under v2, 30.0 under v3). A sudden exit of a third or more of weight, by a price crash or a hosting failure, does the same. What an attacker can do during it is exactly the pause line: proof of work with a 12-hour depth, nothing against any certificate, no new lock to forge; what it cannot do is end the pause early or lock alone, since the departed weight is still in the denominator. The fix is a signed departure (LEAVE, section 5): a key that announces it is leaving is out of every denominator one hour later, which a partitioned key cannot fake.
|
||||
2. **The first 20 to 30 days.** No lock forms before the window holds 30 days of history (spec 03 3.8, `min_daa` = window): the first month is proof of work with the 12-hour depth, by design, and the renter's day counts start at genesis. On day 30 an attacker producing share s of blocks from day k holds s (31 - k)/30 of the window: 75 percent from day 2 locks alone on day 30 (ledger F1).
|
||||
3. **Sybil of keys buys nothing; buying keys buys their blocks.** Weight is blue blocks and every draw is by weight (W6; harness s2). A pool's key with its history can be sold or stolen and is worth exactly its 30 days of blocks, decaying linearly as the window slides (K); the sellers' price, not hash, is the limit, and a seller who keeps a copy strips the buyer by equivocating.
|
||||
4. **Two thirds of total under churn.** The denominator is every key's blocks in the window. If half the honest miners leave, a miner at 51 percent of the old hash holds 51/(51 + 24.5) = 67.5 percent of the window after 30 days and locks alone (spec 03 3.7 item 4: "same as Bitcoin, with a month's warning"). A departure that stops mining and signing at once pauses finality 30 (1 - 1/(3A)) days under rule v2 and until the frozen table expires, 30 days after the last lock, under rule v3 (`finality_horizon.py` C), because a view cannot tell a departure from a partition.
|
||||
5. **A partition longer than a window forks finality.** Each side fills its own table; under v3 neither side under two thirds locks for 30 days after the last common certificate, then both lock alone at once (`results_v2.md` M3) and an operator's trusted certificate resolves it (3.11.4). Under a third of weight, no equivocator shortens that.
|
||||
6. **The proving pool.** Section 1's fake-record capture, until proofs are verified in consensus.
|
||||
|
||||
## 5. What we are building next
|
||||
|
||||
| rank | defence | what it closes | cost | liveness cost |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Proof verification in consensus: a record whose aggregated proof does not verify against the pinned key is invalid | the pool capture of section 1 | 8 to 12 hours; per-record verify time to measure | none |
|
||||
| 2 | Weight-gated deep fork choice: a tip forked more than D (about 10 min) back is a candidate only if the keys that built it hold a third of the weight table at the fork | the pause-time and first-month deep reorg: rented hash has no weight for 10 days, so a 12-h double spend needs the 20-day veto | 10 to 16 hours | none for certificates; a sub-third partition side cannot reorg the other past D, which is the intended outcome |
|
||||
| 3 | Vote-or-burn: a block whose key has participation under 0.5 in its own past burns 20 percent of its producer share | the free pause: silence then costs 7,757 IGN an hour at 34 percent | 6 to 8 hours | none; partition-safe by construction |
|
||||
| 4 | Peer floor and mesh: every box dials three others beside the hands; the node alarms under three peers or two checkpoints without a vote; no `unwrap` on any peer-driven sync path (a pruned node was crashed by one request tonight) | a star fleet pausing on one host; one request crashing any pruned node | 5 to 7 hours + 4 | none |
|
||||
| 5 | The vote signs the execution root too | snapshot poisoning, and "ordered and executed" in one certificate | 8 to 12 hours | lock latency plus executor lag |
|
||||
| 6 | Signalling over 7 consecutive daily windows, the floor a week past the publish | a one-day renter forcing a flip | 3 hours | a week's latency on class changes |
|
||||
| 7 | LEAVE, the signed departure (lane 3's rank 1): a `leave` item carried in blocks, the key out of every denominator one hour after inclusion, sent by the app and the fleet library on a clean stop; F5's trusted certificate implemented | tonight's 30-day-scale pause after a planned departure; sim: first lock 1 h after a 34 to 50 percent departure, 0 conflicts in every partition row (`finality-and-weight.md` 6) | 6 + 4 hours | none |
|
||||
| 8 | A client-shipped certified checkpoint | the cold-start private DAG | 3 hours | none |
|
||||
|
||||
Not adopted: prover attestations as a finality leg (the provers are the miners, coverage is a few percent, every lock would wait on a proof); vesting weight (a bought key transfers vested weight); any automatic rule that keeps locks going after an abrupt departure (a view cannot tell it from a partition: `results_v2.md` L4, M3).
|
||||
|
||||
The honest sentence for the litepaper: a hash majority on Igneum can reorder about two minutes, can buy a veto over finality in twenty public days and then pause it for free, can double-spend at a 12-hour depth only while finality is paused or in the first month, and today can take the proving pool without proving; it cannot reverse a certificate, cannot reach two thirds of weight while honest miners mine, cannot forge state, and cannot change a rule without 95 percent of a day's blocks.
|
||||
112
docs/analysis/block-rate-devnet2.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Block rate on Devnet 2: 10 blocks per second against 1, on the rented fleet
|
||||
|
||||
6 October 2026, the project lead's experiment (through the coordinator, 17:5xZ): "Devnet 2 at a higher block rate for solo miners
|
||||
(Kaspa's answer)". Branch `gpu-fleet`; the node profile on the fork branch `devnet2-bps` (1279a1d6 on release-0.3.14-node
|
||||
4c6b129d): a suffixed devnet started with `IGNEUMD_DEVNET_BPS=10` (or 5) runs `BlockrateParams::new::<10>()` with the
|
||||
TenBps subsidy and target time, Kaspa's Crescendo-style constants for that rate (k 124, merge depth, sample rates,
|
||||
mergeset limit, parents, coinbase maturity from `consensus/core/src/config/bps.rs`); the shared devnet never reads the
|
||||
variable, so the live digest and rules are untouched. Built on igneum-build-1 (`tools/build-remote.sh`). Numbers land
|
||||
below as they are measured; every figure carries its source file under `~/Desktop/fleet/bps/`.
|
||||
|
||||
## The runs
|
||||
|
||||
| Run | Chain | Miners | Length | What is read |
|
||||
|---|---|---|---|---|
|
||||
| A | igneum-devnet-2, fresh genesis, 10 bps | the 38 wave pods plus the 4 Devnet 2 boxes (42 cards, about 2.0 GH/s) | 60 min | blocks per second achieved, blue and red blocks per minute (orphan rate), blue score growth, max reorg depth, checkpoint lock delay and weight at this voter count, p2p bytes per node per minute, node CPU and RSS, exec lag (height minus executed tip), payout intervals per miner from the coinbase records |
|
||||
| B | the same boxes, fresh genesis, 1 bps | the same | 30 min | the same (the control) |
|
||||
| A2 | three relays, 10 bps | the same | dropped: the network lane's read of run A (reds from node throughput, not topology) made a topology run uninformative without per-block CPU profiling on a pod; no provider gave inbound ports for a mesh | |
|
||||
|
||||
Payout interval per tier, from each run's measured block rate and the chain's hash: a 4070 at 28 MH/s, a 5090 at
|
||||
128 MH/s, an 8x 4090 rig at 459 MH/s, at tonight's network hash and extrapolated to 1, 10 and 100 TH/s networks
|
||||
(interval = blocks per second x miner share, the arithmetic in `tools/fleet/bps-collect.py`).
|
||||
|
||||
## Measured
|
||||
|
||||
### Run A: 10 blocks per second, star topology (19:17Z to 20:25Z)
|
||||
|
||||
Seed: the fork's igneumd (83702a35, `IGNEUMD_DEVNET_BPS=10`) on igneum-build-1 (188.40.146.49:26611, a public port), genesis
|
||||
f682b78a4e64f0d2, digest 436d62a5b962e1c2, mining from 19:17:26Z. Miners: dn2-1/2/3 from 19:25Z, the 38 wave pods from 19:26Z
|
||||
(each node beside the pod's live-devnet node on other ports, each dialling the seed only: no pod has an inbound port), 41 peers
|
||||
on the seed. Collector rows every minute in `~/Desktop/fleet/bps/A.jsonl` (the first 20 minutes carry the seed's log counts only;
|
||||
the watch line from 19:37Z after the seed's miner binary was staged).
|
||||
|
||||
| Read | Value | Source |
|
||||
|---|---|---|
|
||||
| Blocks at 10 min (19:36Z) | 5,733 (12.4 blocks/s over the last 159 s) | seed watch line |
|
||||
| Blue score at 10 min | 1,307 (77 percent of blocks red) | the same |
|
||||
| Blocks 19:37Z to 20:24Z (47 min) | 7,204 to 19,153: 4.25 blocks/s; blue +2,926: 1.09 blue/s; red share 77.6 percent | A.jsonl |
|
||||
| Rate by six-minute interval | 7.7, 1.9, 2.9, 2.8, 5.9, 4.2, 3.2, 5.1 blocks/s; blue 0.6 to 1.6/s | A.jsonl |
|
||||
| Tips | 246 at 10 min, 295 to 662 through the hour, rising | A.jsonl |
|
||||
| Difficulty | 6,719 at 19:33Z, 3,551 at 19:36Z, 1,307 at 19:42Z, falling the whole hour (the rule reads the blue rate under target and eases) | seed watch, by hand |
|
||||
| Max selected-chain reorg | 55 blocks (a late pod unwinding its genesis-only view at join) | seed log |
|
||||
| Exec follower | tip 84 at 19:30Z, 240 at 20:19Z: 0.05 blocks/s against 1.1 blue/s; lag 18,901 blocks at the end | igneum_getExecStatus on the seed |
|
||||
| Finality | 18 locks over the run (the voters are the 42 miners' keys) | seed log |
|
||||
|
||||
What it says: with 42 cards on a 10 blocks/s profile the DAG ran at 4 to 12 blocks a second but the selected chain at about
|
||||
1.1 blue blocks a second, three in four blocks red, tips in the hundreds, and the difficulty rule eased all hour because it
|
||||
measures the blue rate. The Horizon network lane's read of the same rows (`docs/analysis/horizon/network.md`): the reds came
|
||||
from node throughput, not the star: the seed spent 61 to 345 ms of CPU per accepted block (mergeset 8 to 200), a 100 ms-per-block
|
||||
knee at 10 blocks/s, RSS 5.4 GB at 12,000 blocks, and the propagation model gives 0.0 percent red for both star and mesh at
|
||||
the measured latencies. So a topology run (A2) proves nothing without per-block CPU profiling on a pod, and was dropped. A
|
||||
true mesh was also not possible tonight: no provider gave a pod with an inbound p2p port, the live devnet has the same star
|
||||
(every rented node dials the two hands and the hub), and the standing-fleet ports rule in `docs/plans/gpu-fleet.md` is the fix.
|
||||
|
||||
The exec follower is the second finding: at 1.1 blue blocks a second it executed 0.05 blocks a second, so a 10 blocks/s chain
|
||||
with payload would leave every wallet and prover reading state hours behind the tip within the first hour.
|
||||
|
||||
### Run B: 1 block per second, the control, same boxes (20:25Z to 20:58Z)
|
||||
|
||||
Seed restarted on a fresh genesis without the profile variable (1 block/s, the devnet's rule), the same 42 boxes, each wiped
|
||||
and rejoined (`FRESH=1`), rows in `~/Desktop/fleet/bps/B.jsonl` from 20:28Z.
|
||||
|
||||
| Read | Value | Source |
|
||||
|---|---|---|
|
||||
| Blocks 20:28Z to 20:58Z (29.5 min) | 2,679 to 5,171: 1.41 blocks/s; blue +2,099: 1.19 blue/s; red share 15.8 percent over the window | B.jsonl |
|
||||
| The first six minutes | 3.05 blocks/s against 1.93 blue/s: the join burst (42 nodes arriving on a chain minutes old, the late ones unwinding genesis-only views; max reorg 16) | B.jsonl |
|
||||
| From 20:34Z on, by six-minute interval | 1.01, 0.99, 0.94, 1.05 blocks/s and 1.01, 0.99, 0.95, 1.05 blue/s: red share under 2 percent | B.jsonl |
|
||||
| Tips | 21 at 20:28Z, 1 to 3 from 20:34Z | B.jsonl |
|
||||
| Difficulty | 19.9 M at 20:28Z, 477 M by 20:34Z, 453 to 482 M after: settled in six minutes and flat | B.jsonl |
|
||||
| Max selected-chain reorg | 16 (the join) | seed log |
|
||||
| Exec follower | tip 188 at 20:28Z, 1,003 at 20:58Z: 0.46 blocks/s against 1.0 blue/s, lag 4,168 at the end and growing | igneum_getExecStatus |
|
||||
| Finality | 0 locks in 30 minutes (the chain was 30 minutes old; the window had not filled) | seed log |
|
||||
|
||||
What it says: the same 42 cards that made a 77 percent red DAG at the 10 blocks/s profile made a chain with one to three tips
|
||||
and under 2 percent red at 1 block/s, with the difficulty settled in six minutes. The exec follower still ran under the chain
|
||||
(0.46 against 1.0), which is the follower's own ceiling on these pods and a finding for the proving lane, not for the rate.
|
||||
|
||||
## Per tier
|
||||
|
||||
From the collector's arithmetic (interval = 1 / (blocks per second x miner hash / network hash)); run A's "blocks" are
|
||||
DAG blocks of which three in four were red, so its payout column is read at a quarter.
|
||||
|
||||
| Network hash | Miner | Run A (4.87 DAG blocks/s, 1.09 blue/s) | Run B (1.19 blue blocks/s) |
|
||||
|---|---|---|---|
|
||||
| tonight, 2.0 GH/s | 4070, 28 MH/s | a block every 0.2 min, of which 1 in 4 pays | a block every 0.8 min, nearly all pay |
|
||||
| tonight, 2.0 GH/s | 5090, 128 MH/s | every 0.1 min | every 0.2 min |
|
||||
| tonight, 2.0 GH/s | 8x 4090 rig, 459 MH/s | continuous | every 0.1 min |
|
||||
| 1 TH/s | 4070 | every 2.0 h (DAG), about 8 h in blue blocks | every 7.0 h |
|
||||
| 1 TH/s | 5090 | every 27 min (DAG), about 1.8 h blue | every 1.5 h |
|
||||
| 1 TH/s | 8x 4090 rig | every 7.4 min (DAG), about 30 min blue | every 26 min |
|
||||
| 10 TH/s | 4070 | every 20 h (DAG), about 3.4 days blue | every 2.9 days |
|
||||
| 10 TH/s | 5090 | every 4.5 h (DAG), about 18 h blue | every 15 h |
|
||||
| 10 TH/s | 8x 4090 rig | every 1.2 h (DAG), about 5 h blue | every 4.3 h |
|
||||
| 100 TH/s | 4070 | every 8.5 days (DAG), about a month blue | every 29 days |
|
||||
| 100 TH/s | 5090 | every 1.9 days (DAG), about a week blue | every 6.4 days |
|
||||
| 100 TH/s | 8x 4090 rig | every 12 h (DAG), about 2 days blue | every 1.8 days |
|
||||
|
||||
Consequence per tier: a home 4070 at a 10 TH/s network waits about three days for a paying block either way (the 10 blocks/s
|
||||
profile's extra DAG blocks are red, so the solo miner's variance does not fall by 10x, only by the blue-rate ratio of 1.09 to
|
||||
1.19, which is nothing); the rig and the 5090 see the same. The way to a shorter wait for the small card is the pool, not
|
||||
the block rate. GHOSTDAG pays red blocks nothing under this rule set, so a higher rate is only worth having where the blue
|
||||
rate rises with it, which needs the node to process a block in well under 100 ms at mergeset 248.
|
||||
|
||||
## The recommendation for mainnet's rate
|
||||
|
||||
From the Horizon network lane (`docs/analysis/horizon/network.md`), carried here as the experiment's recommendation: 1 block
|
||||
per second for the public testnet and the launch; 10 blocks per second behind three gates, each measured before the rate moves:
|
||||
(1) per-block node CPU under 50 ms at mergeset 248 on a laptop core (tonight's seed: 61 to 345 ms, a knee at 100 ms per block
|
||||
at 10 blocks/s, RSS 5.4 GB at 12,000 blocks); (2) finality constants and the clock cap expressed in DAA seconds, so a rate
|
||||
change moves no human-time guarantee; (3) vote aggregation, so 100 voters at 10 blocks/s do not multiply the certificate
|
||||
traffic by ten. Two fleet rules from the runs: a box never mines from a genesis-only view, it syncs first (the 55-block reorg
|
||||
of run A and the 16-block one of run B were late pods unwinding); and the exec follower's rate (0.05 and 0.46 blocks/s on
|
||||
these pods) is the state layer's ceiling and must be measured beside any block-rate change.
|
||||
|
|
@ -181,7 +181,7 @@ speed, so HBM3E is HBM3 here, and 48 Gbps GDDR7 is 28 Gbps GDDR7.
|
|||
|---|---|---|---|
|
||||
| Channels, banks | 64 channels, 1,024 banks | 16 channels (32 pseudo-channels), up to 1,024 banks | 128 channels, 8,192 banks |
|
||||
| Bank-bound ceiling, banks / 45 ns (tRC, the HBM2 and GDDR5-class figure, approximate for both) | 22.8 G reads/s | 22.8 | 182 |
|
||||
| Activate-bound ceiling (GDDR7: 4 per 12 ns per channel, approximate; HBM: 8 per 12 ns per channel, O'Connor Table 2) | 21.3 G reads/s | 10.7 | 85.3 |
|
||||
| Activate-bound ceiling (GDDR7: 4 per 12 ns per channel, approximate; HBM: 8 per 12 ns per channel, O'Connor Table 2). UNMEASURED (6 October 2026, the Horizon lane analysis `docs/analysis/horizon/algorithm.md` section 5.1): the JEDEC HBM2 table gives tFAW 28 ns, 4 activates per channel per window (ICCAD 2021 Table I), which is 2.3 G reads/s for a 16-channel stack, and the one measured random-read rate of an HBM2 part (Shuhai, Alveo U280, FCCM 2020 Fig 7) is 2.4 G, equal to that tFAW ceiling; the 12 ns figure holds only if a bank-interleaved mapping lifts tFAW, which the die enforces per channel. The HBM columns below carry the 10.7 G row as the model's ceiling, unmeasured; an AWS F2 hour (Virtex UltraScale+ VU47P, the same HBM2 subsystem) is the measurement | 21.3 G reads/s | 10.7 (unmeasured; 2.3 at JEDEC tFAW) | 85.3 (unmeasured) |
|
||||
| The ceiling carried below | 21.3 G reads/s (the 5090 measures 17.5, 82 percent of it: the card is already near its memory's activate limit) | 10.7 | 85.3 |
|
||||
| Energy per random 32-byte read (approximate) | 2.0 nJ: 909 pJ activation (one atom per row opened, the HBM2 1 KB row taken for GDDR7's row) plus 4.5 pJ per bit x 256 bits of movement and I/O = 1,150 pJ | 1.2 nJ: the HBM2 sum (909 + (1.51 + 1.17 + 0.80) x 256 = 1,800 pJ) scaled by Samsung's 4.12 / 6.25 | 1.2 nJ |
|
||||
| Static power (refresh, standby, PLLs; approximate, from memory) | 20 W (about 1.25 W per device) | 4 W | 32 W |
|
||||
|
|
@ -191,6 +191,8 @@ speed, so HBM3E is HBM3 here, and 48 Gbps GDDR7 is 28 Gbps GDDR7.
|
|||
| Reads per second per watt at the ceiling (memory, static and controller) | 0.27 G | 0.40 G | 0.49 G |
|
||||
| The 5090 for comparison | 17.5 G reads/s at 326 W = 0.054 G per W; 7,262 reads in flight (17.5 G x 415 ns), 22 per watt | | |
|
||||
|
||||
The FPGA line, public (6 October 2026, the Horizon lane analysis section 5.1): an HBM2 FPGA soft overlay (Alveo U280 or U55C class) carries only the measured row, 2.4 G reads/s per card (Shuhai, FCCM 2020 Fig 7, equal to the JEDEC tFAW ceiling of 2.3 G at 28 ns), which is 0.30x to 0.39x of the RTX 5090 per watt (U55C at 115 to 150 W; 0.20x on the U280). The 11.4 G bank-bound row and the 12.2 G ceiling quoted elsewhere rest on a 12 ns tFAW the JEDEC HBM2 table does not give and are unmeasured until an AWS F2 hour (f2.6xlarge, VU47P, 16 GB HBM2, USD 1.98 an hour on demand) runs the chase kernel at 1 GiB across all 32 pseudo-channels; the lane's pass line is 15 to 25 M reads/s/W, its alarm line 27 (0.5x of the 5090), and over 54 (1.0x) the FPGA lane becomes a Counter ASIC 4.0 item. Consequence per tier: none today (no FPGA mines); if the measured row holds, a soft-overlay FPGA at USD 4,000 to 5,000 a card (approximate) mines at an RX 9070 XT's rate per watt for 7x the price, so no home or rig tier is displaced by it. Ledger M33.
|
||||
|
||||
The GDDR7 system's own power at the 5090's 17.5 G reads/s is 17.5 x 2.0 nJ = 35 W plus 20 W static, 55 W: about 17
|
||||
percent of the card's 326 W (approximate). The other 83 percent is the GPU: 21,760 ALUs spinning at 92.9 percent
|
||||
utilisation on 512 program ops per hash, their register files, schedulers, L1 and L2, and the clock trees, against a
|
||||
|
|
|
|||
161
docs/analysis/ci-failures-2026-10-06.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# CI failures, 4 to 6 October 2026: every non-green run classified, the fixes, the guards
|
||||
|
||||
Written 6 October 2026, 22:0x UK, from `gh run list` (430 runs, every run since the first workflow run at 09:57Z on
|
||||
4 October) and `gh run view --log-failed` on one run per class. Times UTC (UK was UTC+1). This document is an operations
|
||||
record and is listed in `tools/ci/export-exclude.txt`: it is not exported to the public mirror.
|
||||
|
||||
## 1. The totals
|
||||
|
||||
| Workflow | Runs | Green | Failed | Cancelled |
|
||||
|---|---:|---:|---:|---:|
|
||||
| ci | 336 | 205 | 131 | 0 |
|
||||
| windows-ci | 94 | 57 | 20 | 17 |
|
||||
| total | 430 | 262 | 151 | 17 |
|
||||
|
||||
126 of the 168 non-green runs were on master. Master was red without a break from 18:37Z to the end of the evening
|
||||
(20:23Z, run 37526027569) across three causes in a row: the billing block, the copied-sources check, the identity grep.
|
||||
|
||||
## 2. Every failure by root cause
|
||||
|
||||
One line per class. "Guard" is what now stops the class before it reaches master.
|
||||
|
||||
| Class | Runs | First | Last | Cause | Fix | Guard |
|
||||
|---|---:|---|---|---|---|---|
|
||||
| A1 identity grep | 56 | 04 13:26Z | 05 01:46Z | A simulator's run log committed under `sim/difficulty/records` carried the Mac's home path in its first line; 43 master pushes in twelve hours each failed the same step | The log was scrubbed; `.log` files joined the generic scrub (mirror e18256d) | The pre-push gate runs the identity grep locally before any push to master or release-* (`tools/ci/pre-push.sh --hook`), so the hit lands on the pushing machine, not on master |
|
||||
| A2 identity grep | 17 | 05 02:17Z | 05 10:36Z | vmmap dumps under `docs/benchmarks/memory-floods-2026-10-04/vmmap/` carried a local time offset on their Date/Time lines | The dumps were re-stamped to UTC | Same gate |
|
||||
| A3 identity grep | 1 | 06 20:23Z | 06 20:23Z | `docs/analysis/horizon/polish.md`, a research review of internal tooling, quotes the overlay network's product name, the intake key's variable name and the identity guard's own regexes as text; docs/analysis is in the export list, so the grep is right to flag it | The document is listed in `tools/ci/export-exclude.txt`; the identity check and the mirror's `sync.sh` both prune that list, so the guard's purpose (nothing the public reads carries these strings) is intact and the research text is untouched | The exclusion list, plus the gate; `identity-check.sh --self-test` shows an excluded path may quote the patterns and an exported one may not |
|
||||
| B1 hosted runner refused | 32 | 06 18:37Z | 06 20:05Z | "The job was not started because recent account payments have failed or your spending limit needs to be increased": every GitHub-hosted job of every run failed at start with zero steps, 26 master runs and 6 release-0.3.15 runs, and nothing told anyone | Cleared on the billing page by 20:08Z | The `red` job runs on the box's own runner after any failed master or release-* run and posts one line per run (section 4); the `pow` and `sims` jobs can move to the box with one repository variable (section 5) |
|
||||
| C1 copied-sources | 1 | 06 18:00Z | 06 18:00Z | `tools/workers/collect.mjs` mentioned rsync and cargo in a comment; the check read comments | The check skips comment lines | The gate |
|
||||
| C2 copied-sources | 12 | 06 20:08Z | 06 20:19Z | `infra/build-server/repro/rebuild-on-box.sh` clones sources and runs cargo without `touch`; 10 master runs and 2 release-0.3.15 runs | 0f0abc6 (box-work 2bd3bec): the repro script re-stamps its clones. Release-0.3.15 still carries the old script at c25a3ca and will fail this step again until it takes master (or cherry-picks 2bd3bec) | The gate |
|
||||
| D no-foreign-tree-writes | 2 | 06 18:20Z | 06 18:24Z | The new check's warning pipeline (`grep | grep -v | sed | cut`) fails under `pipefail` on a file with no hit, and `set -e` ends the script silently with exit 1 after "self-test passed"; the two runs right after it landed died this way, and the Mac's bash 3.2 died the same way on every tree | `|| true` on the warning pipeline (this change) | The gate runs the check locally, where it would have shown |
|
||||
| E Windows checkout | 3 | 04 13:53Z | 06 20:15Z | Nine screenshot files under `docs/plans/site-ui-3-shots/` carried a colon from an address; git on windows-latest refuses the path, so `actions/checkout` died and with it every Windows build of the tree (the 0.3.15 installer waited on it) | 61f46cc renamed the nine files | `tools/ci/windows-paths-check.sh`: colon and the other forbidden characters, trailing dot or space, reserved device names, over 240 characters; as the pre-commit hook on the staged paths and in the gate on every tracked path |
|
||||
| F1 site build | 5 | 04 13:46Z | 04 13:53Z | `site/scrub-bench.sh` failed on `docs/bench-log.md` and `build.mjs` threw from the execSync | Fixed in the bench log the same afternoon | The gate builds the site in a temporary copy before the push |
|
||||
| F2 site build | 2 | 05 16:42Z | 05 16:43Z | Conflict markers left in `site/journey.json`; `build.mjs` parsed it as JSON | Resolved by hand; `no-conflict-markers.sh` and the first pre-push hook (fb076de) followed | The gate's first check, on every push |
|
||||
| G link check | 1 | 05 09:28Z | 05 09:28Z | `/#wallet` linked from every page with no such id (release-0.3.6) | Anchor added | The gate |
|
||||
| H windows payload inputs | 4 | 05 16:30Z | 06 18:15Z | The signed inputs manifest on the downloads host pinned one node commit and `packaging/windows/node-source.pin` in the tree another: the Mac had pushed new inputs without committing the pin, or committed a pin without pushing inputs | Each time, the pin and the inputs were brought level | Not a tree check and not in the gate: the shipper's push-inputs.sh writes the pin and the commit must carry it; the `red` job now reports the mismatch within a minute instead of the next person opening the Actions page |
|
||||
| I windows installer | 1 | 04 10:32Z | 04 10:32Z | The runner image's Inno Setup was older than 6.3 | The workflow installs Inno Setup when the image's is too old | Resolved in the workflow |
|
||||
| J windows engine | 2 | 04 13:53Z | 04 13:56Z | Rust that did not compile pushed to master (`expected identifier, found keyword let`) | Fixed in the next push | A compile is not a 25-second check; the owner's merge rule (CLAUDE.md, "CI red is stop-the-line") covers it: the merger fixes or reverts inside 15 minutes |
|
||||
| K igneum-census | 1 | 05 23:18Z | 05 23:18Z | igneum-pow's `Instr`, `Program` and a layout argument changed; igneum-census was not rebuilt (release-0.3.11) | Updated with the crate | As J |
|
||||
| L prover-socket | 1 | 06 08:49Z | 06 08:49Z | `tools/proving-v1/pc2-agg-cost.ps1` ran the prover host as root without killing sp1-gpu-server (release-0.3.12) | The playbook was fixed | The gate |
|
||||
| M public API check | 1 | 06 15:12Z | 06 15:12Z | The live observer was 969 s stale when the master-only live check ran | The observer recovered; the hands moved to the box that evening | A live check stays in CI only, master only; it is not a tree fact and not in the gate |
|
||||
| N no-secrets | 2 | 06 15:56Z | 06 16:23Z | A 64-hex test vector next to `private_key` in `app/igneum-wallet/src/vault.rs` (wallet-0.1.5) | Allow-listed as a test value | The gate |
|
||||
| P swallowed defaults line (no CI run: a silent class) | 0 | 06 19:5xZ | 06 21:xxZ | A comment appended to a line of shell assignments in `tools/build-remote.sh` and then `tools/cross-remote.sh` turned every assignment after the `#` into comment text; `bash -n` and shellcheck are silent on it; the default cross-build never ran and its chain kept the previous exes from about 20:40 to 22:00 UK | 36e4ee7 on master: the lines split; `tools/ci/defaults-line-check.sh` with its self-test | In ci.yml at 36e4ee7 and in the gate from this change, so it runs on the pushing machine before the push |
|
||||
| Q gate checks that read the machine, not the fact (found by the gate itself, 22:1x to 22:3x UK) | 0 | 06 21:1xZ | 06 21:3xZ | Two pushes of this change to master were refused by the new hook: (1) git hands a hook `GIT_DIR`, and `remote-run.sh --self-test`'s nested `git init`, commits and reset then acted on THIS repository's worktree: it set `core.bare`, moved the local `master` to three fixture commits and broke the main checkout for ten minutes (restored from the reflog: master back to 36e4ee7, `core.bare false`; nothing was pushed, nothing lost); (2) the same self-test judged a stale `index.lock` by `pgrep -x git` over the whole machine, so the hook's own `git push` (or any other agent's git) made the fixture's checkout die with "index.lock: File exists" | The gate unsets `GIT_DIR` and the other hook variables before any check; the staleness test is the lock's age (over 30 s), and the self-test backdates its fixture lock | The gate itself: every self-test now runs inside a real hook before every master push, with a `git push` alive beside it |
|
||||
| R kill by name (the fleet, no CI run) | 0 | 06 21:09Z | 06 21:09Z | A Mac-side `pkill -f <log file name>` matched nothing: the name was a shell redirect, not part of any command line; the roll-everything script lived on and wiped a box it had been told to hold. Earlier the same day, twice: a `pgrep -f "<literal>"` matched the calling shell's own command line. Six `pkill -f sp1-gpu-server` inside `bash -c '...'` bodies in tools/proving-v1 carried the same shape on master | The fleet runs Mac-side jobs under `tools/fleet/fleet-bg.sh` (a pid file per job) and anchors every on-box kill on the binary's full path and first argument; the six prover lines use `pkill -x` on the binary name | `tools/ci/kill-by-name-check.sh` in the gate: flags `pgrep -f` / `pkill -f` with a plain literal, any pgrep/pkill on a file-name shape (.log, .out, .pid, .json ...), and `ps | grep <literal>`; allows the bracket form, `-x`, `-F <pidfile>`, `kill $(cat pidfile)`, a variable, a full path; self-test of 9 banned and 14 allowed shapes |
|
||||
| O1 windows-ci cancelled | 16 | 04 10:42Z | 06 18:12Z | `concurrency: cancel-in-progress` on windows.yml: a newer master push superseded the run. Not a failure | None needed | None; they are listed because `gh run list` counts them as non-green |
|
||||
| O2 hosted runner not acquired | 8 | 05 19:26Z | 05 20:54Z | "The job was not acquired by Runner of type hosted even after multiple attempts" on release-0.3.10 (7) and master (1): GitHub capacity, retried by hand | Re-run | The `red` job reports it; the box runner for `pow` and `sims` (section 5) takes those jobs off the hosted pool |
|
||||
|
||||
Sum: 168 runs, plus class P, which never reached CI because nothing checked for it. Classes A1 to A3, C1, C2, D, E,
|
||||
F1, F2, G, L and N are 102 runs (61 percent), every one a tree check that finishes in under 25 s on the pushing machine. B1 and O2 are 40 runs (24 percent) of GitHub-side refusals that nobody
|
||||
saw until the Actions page was opened. O1 is 16 runs (10 percent) of expected cancellations. H, I, J, K and M are the
|
||||
remaining 10.
|
||||
|
||||
## 3. The gate: one script, local and CI (`tools/ci/pre-push.sh`)
|
||||
|
||||
Every fast tree check CI runs is in one script. The `site` job of ci.yml calls `tools/ci/pre-push.sh --ci`; the pre-push
|
||||
hook calls `tools/ci/pre-push.sh --hook`. The two cannot drift because there is one list. A check added to ci.yml alone
|
||||
is the wrong place; it goes in the script.
|
||||
|
||||
| Mode | When | What |
|
||||
|---|---|---|
|
||||
| `--hook` on a push to master or release-* | installed by `tools/ci/install-hooks.sh` into the shared hooks directory (one set for every worktree) | all 32 checks; a red check refuses the push and prints its output |
|
||||
| `--hook` on any other ref | same | the two structural checks only (conflict markers, Windows paths) |
|
||||
| `--ci` | the `site` job | all 32 checks, with the site built in place |
|
||||
| default | by hand in any worktree | all 32 checks |
|
||||
| `--self-test` | in the gate itself | a known failure is RED and fails the gate; a known success is ok; master and release-* select the full gate, other refs the light one |
|
||||
|
||||
Measured 6 October 2026, 21:5x UK, on the Mac: 30 checks, GREEN, 25 s (no-secrets 11 s, identity grep 3 s, the rest
|
||||
under 2 s each). The hook never writes into the worktree: the site is built in a temporary copy with
|
||||
`SITE_DOWNLOADS_OFFLINE=1` (063bbca: the earlier hook built in place and rewrote the downloads snapshot in five
|
||||
worktrees); `git status` before and after the full gate is identical.
|
||||
|
||||
Checks that joined CI through the gate and were not in ci.yml before: the ledger sentence check
|
||||
(`ledger-text-check.mjs`), the workflow shell parse (`check-workflow-shell.mjs`), the Windows paths check, and the three
|
||||
self-tests (identity, Windows paths, the red watcher). The swallowed-defaults check (36e4ee7) and the kill-by-name check are in the gate too.
|
||||
|
||||
## 4. The red watcher (`tools/ci/red-watch.mjs`, `infra/build-server/ci-red/`)
|
||||
|
||||
A `red` job in ci.yml and windows.yml runs only when a master or release-* run has a failed job. It runs on the box's
|
||||
own runner (`igneum-build-1`), not on a GitHub-hosted machine, because the hosted pool is the thing that was refused in
|
||||
B1 and O2. It appends one JSON line for the run (id, workflow, branch, commit, title, the failed jobs and each one's first
|
||||
failed step from the run's own API, the URL) to `/srv/ci-red/red.jsonl`, idempotent per run attempt. On the box,
|
||||
`igneum-ci-red.timer` runs the poster every minute as `build`: each line not yet posted goes once to the hidden updates
|
||||
channel through `DISCORD_WEBHOOK_UPDATES` in `/srv/discord-hooks/env`, then its run id is recorded in
|
||||
`/srv/discord-hooks/ci-red-posted.json`. The orchestrator reads the file (`ssh build@<box> cat /srv/ci-red/red.jsonl`)
|
||||
or the channel. No URL is ever printed; a missing key is logged by name.
|
||||
|
||||
Shown on 6 October 2026: the self-test (one line however often `record` runs; the dry run sends nothing; a missing key
|
||||
is named, never a URL; one live send per run; a webhook error keeps the run pending). The poster is installed and
|
||||
active on the box (22:55 CEST, "nothing to post (0 recorded)"). Open: the updates channel has no webhook yet, so the
|
||||
first real red run will land in `red.jsonl` and the poster will log the missing key until `DISCORD_WEBHOOK_UPDATES` is
|
||||
added to `~/.config/igneum/discord` on the Mac and `infra/build-server/discord-hooks/install.sh` is re-run. The
|
||||
Actions-side trigger has not fired on a real red run yet (master was made green in the same change); the first red
|
||||
master or release-* run is its known-failed case.
|
||||
|
||||
## 5. Where CI runs, and why `ci` takes about three minutes
|
||||
|
||||
| Job | Where today | Time on ubuntu-latest | Time on the box (measured 6 October) | Note |
|
||||
|---|---|---|---|---|
|
||||
| pow (igneum-pow `cargo test --release`, packfile test, igneum-census build) | ubuntu-latest | 2 min 30 s to 3 min, cold every run (no cache action) | 42 s cold as the runner user (99 tests), sccache read-only hits after the first build | the long pole |
|
||||
| sims (two Python simulators, --quick) | ubuntu-latest | about 1 min with setup-python and pip | python3 and numpy are on the box from provision.sh | |
|
||||
| site (the gate) | ubuntu-latest | under 1 min | not moved: the live public API check belongs on a neutral egress | |
|
||||
| red | the box's runner | | seconds | only after a failed master or release-* run |
|
||||
|
||||
The workflow now reads the repository variable `IGNEUM_CI_RUNNER`: `box` sends `pow` and `sims` to
|
||||
`[self-hosted, linux, x64, igneum-build-1]`, anything else keeps `ubuntu-latest` (docs/plans/ci-self-hosted.md: GitHub
|
||||
has no fallback in `runs-on`, so a variable is the switch; `gh variable set IGNEUM_CI_RUNNER --body box` as igneum-labs,
|
||||
`gh variable delete IGNEUM_CI_RUNNER` to come back). Recommendation: flip it. The two compile-or-compute jobs are what
|
||||
GitHub's minutes and the billing block were spent on, the box compiles the agents' own pinned rustc 1.99.0, and a `ci`
|
||||
run drops from about three minutes to about one. The hosted runner then serves only the gate and the Windows
|
||||
pipeline (MSVC, WebView2, Inno Setup, PowerShell 5.1, which a Linux box cannot provide). The flip is main's call after the
|
||||
0.3.15 cut, per build-server.md section 7.1.
|
||||
|
||||
## 6. The build box's own red rows (the project lead, 22:3x UK: "also make sure we are fixing and learning from all the errors here")
|
||||
|
||||
Source: `/srv/builds/_log/builds.jsonl` on igneum-build-1, 139 rows from the first build at 18:38 UK to 22:11 UK on
|
||||
6 October; 34 with a non-zero exit. Until this change the box kept no output of a run (it streamed to the agent's terminal),
|
||||
so a red row could not be read afterwards; the classes below are from exit code, duration, compile count and what the next
|
||||
row of the same worktree did. Times UK. "Iteration" = the same worktree and kind green inside ten minutes.
|
||||
|
||||
| Time | Worktree, kind | Exit, secs, compiles | What it was | Kind | Would a local pre-check have saved the round trip | Guard now |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 18:49:12 | build-server, suite `--lib jobbuild` on app/igneum-app | 101, 0 s, 0 | Instant death: `--lib` on a crate whose tests live in the bin (the next row, `--bin`, was green the same minute) | real class: instant | Yes, one second | pre-flight and the kept run log; class `instant` on the row and in the red-run file |
|
||||
| 19:22:14, 19:51:09 | ship0314 then ship0315, prove `--features igneum-prove-host/cuda` | 101, 0 s, 0 (twice) | Instant death, the same command twice on two branches: the feature name (nothing on the box kept the message) | real class: failed the same way twice, instant | Yes | pre-flight refuses a feature the package does not have (`preflight-feature`, exit 3, before the slot) |
|
||||
| 19:26:01 | master, check `-p igneum-prove-core` | 101, 0 s, 0 | Instant death: the package name (no such member) | real class: instant | Yes | pre-flight refuses a `-p` that names nothing (`preflight-package`) |
|
||||
| 19:27:26, 19:27:54 | master, prove | 101, 0 s, 0; then 101, 55 s, 605 | An instant death, then a compile error; green 4 min later | iteration (with one instant) | The instant one, yes | as above; `compile-error` class with the run log kept |
|
||||
| 19:35:56, 19:42:36, 19:48:37, 19:58:12, 21:02:56, 21:47:14 | ship0315, suite and node-linux | 101, 19 to 66 s, 44 to 724 compiles | The shipper's 0.3.15 node fixes: compile and test failures, each green within 1 to 8 min | iteration | No: a Mac check takes 12 to 18 min, the box IS the pre-check | run log kept; class `compile-error` or `test-failure` |
|
||||
| 19:51:09 | ship0315, suite | 2, no duration | `no-dir`: the crate directory was not there, because a second run of the same worktree started in the same second and its checkout was replacing the tree | real class: a shared resource (one worktree directory, two runs) | n/a | one run per worktree directory at a time: `remote-run.sh` takes a per-worktree lock in checkout and run mode and waits (shown: the second of two concurrent runs waited and both finished) |
|
||||
| 19:50:57, 19:52:50 | ca3-v4-node, suite | 101, 12 s and 7 s | Test failures while fixing; green 2 min and 0 min later | iteration | No | run log kept |
|
||||
| 20:45:55 | box-work, night battery dry run | 1, 234 s | The night battery's dry-run subset failed in the box-work agent's hands; its output was on that agent's side | one-off, unread | n/a | the run log is kept from now on; the night battery writes its own report |
|
||||
| 21:01:12 | build-server, `self-test-repro` | 1, 0 s | The repro self-test's first version; green 1 min later | iteration | n/a | none needed |
|
||||
| 21:17:57 | ca3-v4-node, `cargo audit` | 2, 0 s, 0 | Instant death: cargo-audit is not installed on the box | real class: a missing tool, instant | Yes | pre-flight refuses a cargo subcommand the box does not have (`preflight-subcommand`); the install belongs in provision.sh (open: add cargo-audit there, the night battery runs it) |
|
||||
| 21:17:58 to 21:26:42 | finality-pause, two suites alternating | 101, 4 to 26 s | Six reds, the same two commands three times each while the finality pause tests were being fixed; green 2 to 11 min after each | iteration (the first pair outside ten minutes) | No | run log kept; the digest counts the class |
|
||||
| 21:45:13, 21:46:06 | box-capacity, node-linux p2p-probe | 101, 39 s and 3 s | Compile errors; green 1 min and 0 min later | iteration | No | run log kept |
|
||||
| 21:58:26 | ca3-v4-node, check | 101, 12 s | Compile error; green 1 min later | iteration | No | run log kept |
|
||||
| 22:00:08, 22:06:11, 22:08:16 | ca3-v4-node, suite (`finality`, then `clock_rule_v3 a_pause_carries ...`) | 101, 24, 13, 12 s, 22 compiles | The clock rule v3 tests failing while being fixed; green at 22:10 | iteration | No | run log kept |
|
||||
| 22:08:52, 22:08:55, 22:08:57 | ca3-v4-node, three suites in five seconds | 101, 0 s, 0 (three times) | Three instant deaths in a row with nothing compiled: cargo refused before building (a manifest or lock the overlay carried mid-edit, or a name); the three commands were green one minute later, so the tree was fixed under them | real class: instant, failed the same way three times | Yes, one second each | pre-flight (manifest, package, feature, subcommand) before the slot; class `instant` flagged; the run log kept so the next one is read, not guessed |
|
||||
| 22:11:38 | ca3-v4-node, check `--tests` | 101, 49 s, 844 | A compile error in the test targets; still red when the log was read | iteration, open | No | run log kept |
|
||||
|
||||
Sum: 34 red rows. 22 are iteration (an agent taking a red to green inside ten minutes, the box doing the compile the Mac
|
||||
cannot do in time). 12 are real classes, in three families: instant deaths (8 rows: a feature, a package, a target, a
|
||||
tool, and three unread), a shared worktree directory (1 row), and the night battery's unread dry run (1 row, plus two
|
||||
instant ones inside iterations). Every real class now has a guard in `infra/build-server/remote-run.sh`, shown on the box
|
||||
in a sandbox (a pass, a failing test, an empty filter, a missing package, a missing feature, a missing subcommand, a compile
|
||||
error, a broken manifest, two concurrent runs of one worktree):
|
||||
|
||||
| Guard | What it does | Class it stops |
|
||||
|---|---|---|
|
||||
| pre-flight | before the slot's time is spent: `cargo <sub>` exists, the manifest parses (`cargo metadata --no-deps`), every `-p` is a member or a dependency, every `--features pkg/feat` exists; a refusal is exit 3 in about a second with the reason | instant (manifest, package, feature, tool) |
|
||||
| kept output | the last 400 lines of every run in `/srv/builds/_log/runs/<id>.log`, named in the row (`run_log`) | every unread class |
|
||||
| class on the row | `class` in builds.jsonl: compile-error, link-error, test-failure, instant, slot-timeout, no-dir, no-test-matched, preflight-*, other | the digest can count what kind of red it was |
|
||||
| no-test-matched | a `cargo test` with a filter that ran 0 tests in every binary exits 3 instead of a green "0 tests" | a wasted round trip that read as ok |
|
||||
| one run per worktree | a per-worktree lock in checkout and run mode; the second waits up to 2 h and says so | no-dir, the half-replaced tree |
|
||||
| the red-run file | every red row is appended to `/srv/ci-red/red.jsonl` (the file the CI watcher writes), `"source":"box"`; not posted alone | |
|
||||
| the daily digest | at the first pass at or after 09:00 London, one line to the updates channel: reds in 24 h, CI and box, per class, with each class's guard | the learning, read once a day |
|
||||
|
||||
## 7. What belongs on another branch
|
||||
|
||||
| Branch | One-line change |
|
||||
|---|---|
|
||||
| build-server (provision.sh) | install cargo-audit for the build user (the night battery and the ca3 lane call it; pre-flight now refuses it with a clear line instead of a 0-second exit 2) |
|
||||
| release-0.3.15 | take master (or cherry-pick 2bd3bec): `infra/build-server/repro/rebuild-on-box.sh` re-stamps its clones, else the copied-sources step fails again at the next push. Its own `tools/ci/windows-paths-check.sh` (c25a3ca) is superseded by master's: on merge keep master's file and drop the extra ci.yml step, the gate runs it |
|
||||
231
docs/analysis/horizon-2026-10.md
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# Horizon, October 2026: the ranked research across every system
|
||||
|
||||
Written 6 to 7 October 2026 by the Horizon coordinator (branch `horizon`, worktree `igneum-wt-horizon`) on the project lead's ask of 6 October 2026, 20:4x UK: "deep backward and forward predictive research and modelling for our algo and all of our systems: is there room for improvement, room for more coin utility, algo improvements, security improvements, anything we can do to make a 51% attack impossible, basically creating a level of polish that has not been seen before." Later the same evening: "research anything else that we can research too, predictions, forward thinking, what we can actually do that has not been done or applied, think outside the box", "be revolutionary", and "if we create a new way of hashing or a new way of proof of work to revolutionise the space then that's absolutely fine, I want you to deploy everything to create something that has not ever been done before."
|
||||
|
||||
The bar main set, and the bar this document holds every claim to: "impossible" is not available to any proof-of-work chain. The bar is that a majority of hash buys nothing: it cannot reverse what finality locked, cannot forge a proof the nodes re-execute, cannot change a rule without 95 percent signalling, and loses more than it earns. Every claim here is a number with a model or a simulation behind it, priced in rented hash at the measured 6 October rate (`docs/bench-log.md`, "Rental cost of hash, 6 October 2026": USD 0.0117 per MH/s-hour, so USD 11.7 per GH/s-hour and about USD 281 per GH/s-day; the live devnet at 1.16 GH/s), with its consequence per tier and what to build. Hours are agent hours (the project lead's rule: Claude-side work takes hours, never weeks).
|
||||
|
||||
## The lanes
|
||||
|
||||
| Lane | File | State |
|
||||
|---|---|---|
|
||||
| 1 consensus-security | `docs/analysis/horizon/consensus-security.md`, and the paper `docs/analysis/51-percent.md`; models `sim/horizon/consensus-security/` | landed, commit eec2cd7 |
|
||||
| 2 algorithm | `docs/analysis/horizon/algorithm.md`; model `sim/horizon/algorithm/model.py` | landed, commit 5ff7393 |
|
||||
| 3 finality-and-weight | `docs/analysis/horizon/finality-and-weight.md`; models `sim/horizon/finality-and-weight/` | landed, commit c3aa502 |
|
||||
| 4 economy-and-utility | `docs/analysis/horizon/economy-and-utility.md`; models `sim/horizon/economy-and-utility/` | landed, commit 4617a01 |
|
||||
| 5 network | `docs/analysis/horizon/network.md`; the experiment `docs/analysis/block-rate-devnet2.md` | landed, commits 3777014 and b965b64 (runs A and B; A2 dropped) |
|
||||
| 6 polish | `docs/analysis/horizon/polish.md` | landed, commit ac4cc93 (95 ledger rows) |
|
||||
| 7 frontier | `docs/analysis/horizon/frontier.md`; model `sim/horizon/frontier/frontier_model.py` | landed, commit 5ff7393 |
|
||||
| 8 new-proof-of-work | `docs/analysis/horizon/new-pow.md`; prototypes `proto-newpow/` | landed, commits cbcff47 and 92db7c5 (two prototypes measured on rented 4090s, verdicts in section 6) |
|
||||
|
||||
## 1. One page for the project lead
|
||||
|
||||
Closed 6 October 2026, 22:3x UK, every lane landed.
|
||||
|
||||
**Standing decisions (the project lead, 6 October 2026, 22:3x UK).** Verbatim: "Fees cannot fund security for a decade" and "Miners need to be the security". Meaning: miners are the security always, no time bound, no stake, no outside checkpoints or committee. The chain pays its own miners from emission plus fees; nobody pays upkeep, not the founder, not a treasury, not a dev fund. Self-sustaining means the emission curve keeps mining worth doing on its own for as long as fees are small, which lane 4 measures as a decade or more, so emission never decays on a schedule that assumes fees take over. Fee revenue is never assumed as the security budget in any model or public sentence. His third line, "Wrong constants and claims in our own text", acknowledges lane 6's finding; the nine ledger rows in item 9 are the fix.
|
||||
|
||||
1. **The proving pool was the one line where a majority earned more than it spent**: 11,636 IGN an hour at 51 percent of blocks. Fix: the proof verified in consensus. **In 0.3.16** (fork exec-sync-0313 da2d17ec), switch `proving_consensus_verify_daa` off by default. **Decision owed:** when it activates.
|
||||
|
||||
2. **Tonight's two-hour finality pause.** 42.7 percent of the frozen voter table left in three minutes and the frozen table held the pause a window (30 days on mainnet). Fix: a signed `leave` item, the key out of every denominator an hour later. **In 0.3.16** (finality-pause-node 766e70ca, finality-pause 27f82ec). The slices-under-10-percent rule: **done**.
|
||||
|
||||
3. **A 51 percent attacker buys a 90-second reorder window and nothing past a certificate.** **Done:** the peer-driven unwrap class (8e2f5cbe, 0.3.16), the receive-side version gate (f1ea7a38, 0.3.15). Next: weight-gated deep fork choice and vote-or-burn.
|
||||
|
||||
4. **The chip is settled in kind, open in degree**: 5.7x per joule at class v3, 2.1x at v4. The lever is the latency-shadow size N as a genesis ladder, each step by 90 percent signal. Text (M32, M33): **done**. **Decision owed:** the N ladder at genesis.
|
||||
|
||||
5. **Fees stay tiny for about ten years; emission carries security, with no end date.** USD 450 a day at launch and 4,200 in year 5, against 54,800 and 13,700 of emission. Text (E19, E20, E21, P24, P25): **done**. Next: unproven credit rolled forward, the job price decoupled from `f_p`.
|
||||
|
||||
6. **The signalling window could be bought for a day.** Seven consecutive daily windows at 95 percent, the floor a week past publish: **in 0.3.16** (ca3-v4-0316 0760b844). The thresholds in one sentence (G15): **done**.
|
||||
|
||||
7. **New proof of work, measured.** Scheme A (mining is proving): never. Scheme B (tensor shadow): never as class content; kept as reserve R8. Scheme C (dataset from stored state): the class v5 candidate; hash rate and watts unchanged, build +1.4 ms, verifier +0.11 to 0.21 ms per unit.
|
||||
|
||||
8. **Block rate: 1 a second for the testnet and launch.** 10 a second on Devnet 2 ran 77.6 percent red on node cost. 10 waits behind three gates: per-block CPU under 50 ms on a laptop core, finality constants in DAA seconds, vote aggregation. **Done.**
|
||||
|
||||
9. **Polish.** Pause wording in Ember and the API: **in 0.3.16** (ember-tune b726ce4). `fork_is_close` and the publisher's activation guard: **in 0.3.16** (1357d28, 08f5276). Export-disk cap: **done** (gpu-fleet f9ad70e). Nine text corrections (M32, M33, F26, E19, E20, G15, P24, E21, P25) plus X31 to X33: **done**. Unsigned installers: **decision owed**. Relay fixes (28c028b) on fud-close: merge owed.
|
||||
|
||||
**Decisions owed from the project lead:** the cryptanalysis spend (USD 80,000 to 160,000); the testnet date word; the N ladder at genesis; the verification switch activation.
|
||||
|
||||
## Decisions ready for the project lead (added 6 October 2026, 23:1x UK)
|
||||
|
||||
Two one-page verdicts landed after the close. Each is quoted verbatim from its file and each is **the project lead's decision owed**. The files sit on their branches and are not merged; read them there.
|
||||
|
||||
### Emission: the tail
|
||||
|
||||
`docs/analysis/tail-emission.md` on branch `tail-emission` (e1e28d2), ledger E22. The one page at the top of the file, 598 words:
|
||||
|
||||
> **The problem.** Today's schedule pays miners USD 210,513 a day in year 1 at a flat USD 0.10, 6,877 in year 11, 427 in year 20, 13 in year 30. A 24-hour 51 percent rental costs 11.8 days of budget at any price and any hardware, so the attack costs USD 2.5 M in year 1, 81,106 in year 11, 5,042 in year 20, 158 in year 30. Year 20 pays 0.2 percent of year 1.
|
||||
>
|
||||
> **Not a fix.** Fees (USD 450 a day at launch, 4,195 in year 5, under 1 percent of it to a miner; Bitcoin's fee share is 0.69 percent), a four-year halving (the same cliff later), a treasury (emission with an owner).
|
||||
>
|
||||
> **The safe baseline.** A tail denominated in supply: p percent of scheduled supply a year for ever, from the first month the curve pays less. Security becomes a fixed share of market cap in every year: at p = 1 a 24-hour attack costs 0.032 percent of market cap, the 20-day veto 0.6 percent, at any price, no oracle. The holder gives **0.99 percent of their share a year** (9.5 percent over ten years), nothing else. No rent, no forced movement, no cost to inactivity.
|
||||
>
|
||||
> **The revolutionary candidates, tested.** A hash thermostat (E) beats its withholding game only with presence pay and reads hardware as money: a 10x efficiency jump halves its budget for two years. Settled-value targeting (F) needs 8.5 percent inflation at velocity 1 and a wash trader pins it to its ceiling for one base fee. Security as a product (H) is 1 percent of the subsidy in year 1; dormant-coin rent (G) is refused by ruling. The hybrid (I) holds under every modelled attack and adds nothing to the floor.
|
||||
>
|
||||
> **The supply question.** The coin count is cosmetic (18 decimals); the unit price is social. Kaspa and Dogecoin paid blocks in the hundreds of coins and drew miners. A solo 100 MH/s card on a 100 GH/s network earns 2,190 IGN a day today, 6,912 at 100 a block. A monthly glide loses 2.9 percent a month and halves nobody's income overnight.
|
||||
>
|
||||
> **Recommendation, one coherent emission, every field a genesis parameter:** 100 IGN a block at one block a second; a monthly glide with a two-year half-life (Kaspa's shape at half its pace); a 90-day ramp from 10 percent; a 1 percent tail from the month the glide first pays under 1 percent of supply (year 11.4). Supply: 8.64 billion at the switch, 9.5 billion in year 20, 11.6 billion in year 40. The budget never falls under 1 percent of supply, tested to year 200.
|
||||
>
|
||||
> **The public sentence** (replaces "4 billion, approached and never reached"): *Igneum has no hard cap. Emission starts at 100 IGN a block and falls 2.9 percent a month for ten years, then runs at 1 percent of supply a year for ever, every coin to the miners and provers who secure the chain. A holder's share falls 1 percent a year, and a 24-hour attack costs about twelve days of emission at any price, in any year.*
|
||||
>
|
||||
> **What moves.** The emission constants of `igneum.rs` become one parameter set, `EmissionSchedule::CURRENT` (section 5 names each); the testnet carries `TESTNET_1`. Coded on fork branch `tail-emission-node`; the devnet digest is pinned unchanged by test; the testnet digest moves. A genesis decision for `igneum-testnet-1`, nothing live touched.
|
||||
>
|
||||
> **Risks.** "No cap" is a sentence critics quote (E1 already concedes Monero's trade). The 1 percent is a judgement inside the peer range (Monero 0.85, Bitcoin 0.83, Ethereum about 0.5). Year 1 stays front-loaded (26 percent; 47 in two).
|
||||
|
||||
### Vote or burn
|
||||
|
||||
`docs/analysis/vote-or-burn.md` on branch `vote-weigh` (e816f28). The verdict, 498 words:
|
||||
|
||||
> **Neither mechanism closes the line. Both price silence. Only the bonus passes the 95 percent test, and only at genesis.**
|
||||
>
|
||||
> 1. **Honest silence, measured.** 10.4 percent of the 83 staying keys' 45,176 key-checkpoints over 24 hours sat under participation 0.5; 79 percent of that is 15 fleet keys the Devnet 2 gate swapped off the live chain and back, mining nothing while away. The 19 always-on desktop keys dipped under 0.5 in 3.4 percent of checkpoints, longest dip 34 minutes, which on mainnet's 2-hour window never reaches 0.5. Tonight's 20 silent keys mined nothing while silent, so the burn would not have shortened the pause.
|
||||
>
|
||||
> 2. **History.** Every reward cut was fought and lost by the miners who stayed (Ethereum three times, Zcash three times, Ergo at 92 percent); where a cut removed a class's subsidy, the class left (Decred: two thirds of hash gone in a month). Offline penalties are accepted only as the reward forgone (Ethereum 0.625 against 0.844 earned; Cosmos 0.01 percent). No proof-of-work chain takes coins from a found block for a liveness fault.
|
||||
>
|
||||
> 3. **The 95 percent test.** Respect: 7-day hash at least 95 percent of the week before, no fork over 5 percent at 30 days, 95 percent of weight signing. Estimates: burn 70 to 80 percent (a 20 percent cut of a found block for an outage, without precedent, and for ever from `--no-vote` miners and pool members without a verifier). Bonus at genesis 93 to 97 percent (nobody loses what they had). Bonus by signal after launch 85 to 90 percent. A bonus from the proving pool: 97 percent, deters nothing.
|
||||
>
|
||||
> 4. **Security.** Weight is blue blocks (W2); an unpaid block is still weight, so a silent third keeps the veto under every mechanism. Base: the producer share. The burn prices a 34 percent set's silence at 6,206 IGN an hour (51-percent.md's 7,757 was 20 percent of the whole subsidy), the bonus at 3,103: USD 372 or 186 per 12-hour pause at USD 0.005; a deposit worth a double spend covers either. Weight-gated deep fork choice removes the prize; LEAVE ends tonight's class of pause.
|
||||
>
|
||||
> 5. **Recommendation.** No burn: `vote_burn_bps` stays never and leaves the tree before the testnet code is public. The bonus, `signing_bonus_bps` 1,000 (node tree ca3-v4-0316 10db4b61), on from the testnet genesis as the schedule, never by signal: silence = a voter of the table at the latest checkpoint in the block's past with no vote in the presence window (7,200 s, 240 indices); a young key and a key under dust read as present; the unsigned tenth to the proving pool. Gate before the switch leaves never: the replay test of section 5, then the owner's word. The miner's sentence: **"Igneum pays 80 percent of each block to its miner. 8 of those 80 points are for signing finality, which your miner does by itself every 30 seconds. Miss two hours of signing and your next blocks pay 72 until you sign again. Nothing is burned."**
|
||||
|
||||
### Open questions, being implemented
|
||||
|
||||
- The N ladder list (the era-draw ladder of the algorithm lane): being implemented; the one page's owed item is its values at genesis.
|
||||
- 18 decimals: being implemented.
|
||||
|
||||
## 2. The ranked list: top 25 across every lane
|
||||
|
||||
Rank is payoff over cost across lanes, with safety first, then liveness, then money, then text. "L1 r3" means lane 1's own rank 3; the lane file holds the full evidence row.
|
||||
|
||||
| Rank | Item | Lane | Evidence | Model | Hours | Consequence per tier | What to build | Gate |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 1 | Verify the aggregated segment proof in consensus; a record whose proof fails against the pinned aggregator key is invalid | L1 r1 | spec 07 7.7 items 3 and 4; P21; a producer captures its block share of the 20 percent pool with fake records | capture = pool x (H inside the window + most outside): 11,636 IGN/h at 51 percent; SP1 light-verifier cost per record 1.8 to 2.4 s measured (bench-log) | 8 to 12 | prover: paid only for proofs; holder: the pool is real; node operator: one verify per record on the proof pool thread; miners: nothing | the verify call in the record check of `consensus/core` proving, the v0 per-shard record payout-only and capped at the exclusive window | fast-time: a fake-proof record is rejected by every node and the producer loses the block; a true record pays |
|
||||
| 2 | The departure announcement: a `leave` item (key, DAA score, signature) in blocks; D = 1 h later the key is in no denominator, sliding or frozen, and its votes are invalid; app Stop and the fleet library send it | L3 r1, L1 r8 | tonight's pause (L3 3.1): 42.7 percent left in 3 min, the frozen table held a window; sim T: first lock 1 h after a 34, 45 or 50 percent departure, 0 conflicts in every partition, eclipse and equivocator row | `finality_horizon.py` seeds 7, 11, 13 | 6 (+4 for F5's trusted certificate) | holder: a 30-day mainnet pause becomes 1 h when leavers are honest; a silent leaver still costs a window; pool and rig: one message on a clean stop; attacker: buying keys to leave them gains nothing (w + L must still reach 2/3) | the item in the coinbase finality section, the denominator rule in `finality.rs`, the send in Ember and `tools/fleet/lib` | harness: 45 percent leaves with leaves, lock within D + 1 checkpoint; 0 conflicts in 50/50, 60/40 and the 34 percent eclipse |
|
||||
| 3 | Signalling over 7 consecutive daily windows at 95 percent, the floor no nearer than 7 days past the publish, the stale-box list empty before the floor | L1 r6, L4 4.5 | the P2 one-day window is buyable: 19 N of hash for 24 h (USD 534k at 100 GH/s) forces a flip onto an unready fleet; a 6 percent holdout buys delay for nothing | `signalling.py`, `signal_game.py` | 3 | pool and rig: a week more before a class change and a week of visible share; home miner: a week to update; attacker: the bill x7 in public | the window count in the P2 rule, spec 5.7 text, one sentence carrying 60 / 90 / 95 in spec, CLAUDE.md and the litepaper | fast-time: 6 of 7 days at 95 percent does not flip; 7 does; the harness's failed case |
|
||||
| 4 | Weight-gated deep fork choice: a tip whose fork point is older than D (10 min of past-median time) is a candidate only if the blocks on it since the fork were produced by keys holding at least 1/3 of the weight table at the fork point | L1 r2 | during a pause or the first 20 days a renter with fresh keys double-spends at the 12-h depth for USD 146 at 1 GH/s | `cost_model.py`; deterministic over the block's past | 10 to 16 | holder and exchange: rented hash cannot reorg past 10 min even while nothing locks; honest miners: nothing (their keys hold the weight); new chain: the first 20 days gain a bound they lack today | the candidate filter in GHOSTDAG tip selection over the certified weight table | fast-time: a 51 percent fresh-key fork from 15 min back is refused by every honest node; a 1/3-weight fork is accepted; partition heal unchanged |
|
||||
| 5 | Vote-or-burn: a block whose producer key has participation under 0.5 over the presence window in its own past burns 20 percent of its producer share | L1 r3 | the pause as a liveness attack has zero marginal cost once the veto is held | 0.2 x A x 0.8 x subsidy: 7,757 IGN/h at 34 percent (USD 155/h at 0.02) | 6 to 8 | attacker: a pause costs; honest home miner: nothing while signing (the official client signs every checkpoint); pool user: the pool's participation; partition-safe because the test is the block's own past | the participation read in coinbase validation, the burn in the subsidy split | fast-time: a 34 percent silent set's blocks pay 80 percent; a 50/50 partition burns nothing on either side |
|
||||
| 6 | Roll unproven shard credit into the next proven segment's pool instead of the escrow for ever | L4 r2 | a ten-day prover refusal strands 547,570 IGN a day (5.5 M IGN) with no rule; spec 5.3, 7.7 item 3, 7.8 item 7 are silent | `stress.py` refuse scenario, `stranded_share` | 8 | prover: credit is never lost to the market's slow days; holder: no undecided burn; rollup customer: nothing | the roll-forward in the pool credit split, spec 5.3 text | fast-time: 100 unproven then 10 proven segments return the escrow to zero |
|
||||
| 7 | Decouple the job price from `f_p`: reserve = measured proving electricity per pgas at the published settlement rate (spec 5.10.3), the 1.5 premium becomes a bid | L4 r1 | at the adopted floor a billion-cycle job is 15 IGN = USD 0.075 / 0.30 / 1.50 against Boundless's 0.21 (approximate); above USD 0.014 per IGN the chain overprices by rule | `utility.py` sections 1 to 2 | 16 | rollup customer: a price that can clear; prover: still paid above electricity; holder: more jobs, more burn | the reserve rule in spec 5.4 and 5.11, the bid field on the job | a simulated job book clears within 20 percent of Boundless's median at all three prices |
|
||||
| 8 | Operational rule, no protocol change: a standing box never leaves the live chain for an experiment; any orchestrated departure over 10 percent of weight is staged in slices under 10 percent an hour; the fleet library refuses to swap a standing box's chain | L3 r2 | the rehearsal took 36.5 percent at once; sim L2 and M4: a gradual departure costs nothing because every lock re-freezes the table | spec 3.7 item 2 | 1 | fleet operator: finality stays on; every tier: no pause from our own experiments | a refusal in `tools/fleet/lib` plus a `--staged` departure | the next rehearsal keeps `finality_active` true |
|
||||
| 9 | Peer floor and mesh, and no `unwrap` on a peer-driven path with a sync-request fuzz gate (tonight's pruned-node crash: `consensus/src/processes/sync/mod.rs:87`, plus 27 sibling sites listed in L1) | L1 r4 | run A's star (321 tips, 77 percent red); the hub crash at 19:57Z: any peer can crash any pruned node at zero hash cost | a star with its hub down is n islands; the sibling list by file:line | 9 to 11 | node operator: no crash from a peer's request; home miner and rig: the app alarms under 3 outbound peers or 2 checkpoints without a vote; fleet: no star | the fleet lib dials 3 boxes beside the hands; the node alarm and app line; the unwrap sweep; the fuzz in `tools/ci` | the fuzz runs the sync request space against a pruned node with no panic; the alarm fires on a known-cut case and stays quiet on a healthy one |
|
||||
| 10 | N (the latency-shadow size) as a genesis ladder per era {100k, 130k, 200k, 330k, 650k, 1.0M}, floor and ceiling fixed at genesis (10x verifier headroom on the 2.5x rule; O-1.14 sets the ceiling), one era draw consumed as for `epoch_len`, each step by 90 percent miner signal over 7 days, never unconditional | L2 r5, L7 finding 1 | HBM4 raises the stored-dataset chip's bare edge (4.9x to 12x depending on tFAW, L2 and L7 disagree and both are labelled); N = 100k holds the chip at 2.1x on GDDR7 at k = 1, N = 330k at 1.3x; an unconditional doubling retires the M5 Max at era 1 (-10.5 percent at 200k) | `model.py --section ladder` (L2 5.3a, the reconciled table) | 6 to 8 | 100k to 130k: the M5 Max -3.3 points, nobody else; 130k to 200k: M5 Max -6 more, the 5090 -2.7 at its cap, the 4070 +21 W; 200k to 330k: compute-bound on every capped NVIDIA card; pool users nothing at any step; verifier +0.17 to 0.56 ms per warp | the ladder in the era-draw table of spec 1.13, the step rule on P2's mechanism | per step: every public-benchmark card within 5 percent of its previous-step rate, bit-exact on three vendors, verifier under 10 ms cold on the O-1.14 core |
|
||||
| 11 | Aggregate-first vote verification and aggregated in-block carriage as the mainnet default (spec 3.4.2 item 2, decided) | L3 r3 | per-vote verification is 1.6 s per checkpoint per node at 1,000 voters and about 13 s at 8,192 (approximate); measured lock delay p50 0.86 to 1.26 s at 4 to 93 voters on node 1 | L3 4.3 table; blst 0.3.17 `fast_aggregate_verify` already in the fork | 8 | node operator on a laptop: a checkpoint costs one pairing, not a thousand; every tier: lock delay stays under 3 s at 8,192 voters | batch votes per (index, hash), verify once, bisect on failure; the in-block aggregate | fast-time with 1,000 and 8,000 synthetic voters: p50 lock delay under 3 s, CPU under 25 percent of a core, 0 conflicts |
|
||||
| 12 | The finality weight table carried inside the recursive segment proof, updated one mergeset per segment: a consensus proof at mergeset cost | L7 r1 | phase two's hardest item (P4) becomes incremental on code that exists; about one BLS verify per 30 s (one shard's budget, approximate, unmeasured) | `frontier_model.py` | 60 (first 10: measure) | phone wallet and bridge: finality from one proof, no node trusted for the voter set; rollup customer: one object; prover: one more public-value block per segment | the W2 transition in the aggregator guest, the GHOSTDAG colouring check in-guest | first: BLS verify and colouring cycle counts inside the SP1 guest on a 24 GB card within 2x of the estimate; then a proof per checkpoint under 30 s on a proving-only 5090 |
|
||||
| 13 | Reproducible-build attestations in a registry contract; Ember refuses a release under N of M attestations | L7 r3 | ledger G7, G13: the update key is an operator channel; Bitcoin's guix.sigs is the precedent with the chain as the sigs repo | text and contract | 12 | every tier: an update needs N independent builders, not one key; fleet: the box's reproducible Windows and Linux builds (CLAUDE.md 6 Oct) are the inputs | the contract, the builder tool, the Ember check | a release with 1 of 3 attestations is refused by Ember; 3 of 3 installs; the known-failed case |
|
||||
| 14 | Close O-1.14 with a real 2019 laptop run; adopt the half-core proxy as the standing stand-in; dr736 out, dr368 in as R0 | L2 r1 | box proxy: dr736 10.51 ms cold and 15.49 half-core; class v4 5.06 and 8.23 | L2 5.5 | 2 | miners 0; pool verifier cores x1.3 at dr368 instead of x2.4; a 2019 node keeps 1.8 ms under the gate on class v4 | Windows cross build on the box, relay to the laptop, bench, log | ms per warp under 10 cold on the real core for class v4 and dr368 |
|
||||
| 15 | The vote signs the execution root too (chain id, index, block hash, post_root); a certificate pins the state; snapshots check against the last certificate | L1 r5 | snapshot poisoning: a wrong state above the pin is undetectable today | every voter executes natively (spec 07) | 8 to 12 | holder: a lock is a state lock; node joining from a snapshot: cannot be poisoned; cost: exec lag enters lock latency | the vote message, the pin rule, the snapshot check | fast-time: a poisoned snapshot node cannot join the quorum; honest lock delay rises by the measured exec lag only |
|
||||
| 16 | `finality_provisional` reported beside `finality_active`, never as a lock, plus a detector-driven `security_alert` (correlated group over 1/3 of a day's blue blocks; pause; conflict) shown by wallets and the explorer as "confirm at 12 h" | L3 r4, L1 r7 | sim P: provisional conflicts in every partition (516 to 1,062 per 360 min), final 0; the exchange guidance exists only as text today | L3 4.1; the detector of counter-asic-3 | 3 + 4 to 6 | exchanges: a third row in the guidance; holder: told when to wait; home miner: a line in Ember | the RPC fields, the explorer and wallet copy, spec 3.9 row | forced pause: explorer and wallet show provisional and the alert; a healthy day shows neither |
|
||||
| 17 | Every miner-signalled execution parameter enters the consensus digest the same release, with a `tools/ci` check that fails a `Params` field marked signalled and absent from the digest | L4 r5 | signal-then-defect on an execution parameter is a silent state fork unless the handshake refuses the defector; `Params.fees` is in the digest, nothing guarantees the next | `signal_game.py` section 3 | 6 | node operator: no quiet fork; every tier: a defector is refused at the handshake | the check, the digest rule in spec 5.8 | the check fails on a planted field and passes on the live set |
|
||||
| 18 | The 80/20 split as a 60 percent parameter inside a hard band [10, 30] percent | L4 r4 | not load-bearing at launch traffic; 30 percent buys backlog relief at 100 shards a block (economy-2026-10-04 5.3) | `stress.py` | 10 | prover and miner: a split the market can move inside a band nobody can break; holder: the cap and the halving untouched | the band in spec 5.3 and 5.7 | harness: a 60 percent vote reaches 30 percent; a 100 percent vote cannot pass the band |
|
||||
| 19 | A WebAssembly verifier of the wrapped block proof in the tab, with the millisecond count shown | L7 r4 | three working precedents (Helios WASM, ziren-wasm-verifier, wasm-groth16-verifier); the certificate half already runs at 58 to 68 ms warm, 139 to 155 ms cold (bench-log round 6, P3) | text | 16 | holder and rollup customer: verify in the browser; the P3 wrapper measurement comes forward | the Groth16 or Plonk wrapper, the WASM verifier on the site | a block proof verifies in the tab under 500 ms on a laptop; the measurement labelled on the page |
|
||||
| 20 | Ember as node, wallet and light client for everyone | L7 r5 | 1,000 testnet miners become 1,000 verifying nodes; Monero shows about 5,000 peers (monero.fail), Ethereum 8,136 execution nodes (ethernodes), approximate | text | 20 | home miner: one app; node count = miner count; Sybil counts of nodes irrelevant by design; node cost per lane 5's tiers | the node and wallet surfaces inside Ember | a fresh Windows install mines, verifies and shows a balance in the first 60 s with no second download |
|
||||
| 21 | Work-stake: vote weight as the external-job bond, with the spec sentence "no coin stake; the only thing at stake is 30 days of public work" written first | L7 r2 | a 0.1 percent key has about 16,427 IGN of 30-day pool income plus its vote at risk against a designed 0.0015 IGN coin bond per job | `frontier_model.py` section 3 | 24 (prototype) | prover: a bond nobody can buy; rollup customer: a griefing cost that scales with the prover's standing; holder: "no stake" stays true as "no coin stake" | the bond rule in the job claim | a griefed job costs the griefer its weight for 30 days in the fast-time harness; the spec sentence lands before the prototype |
|
||||
| 22 | Client-shipped certified checkpoint (index, hash, voter-table digest) refused if missing from the DAG; exec generations spaced geometrically to the finality depth (about 16, 1.8 GB today) | L1 r9, r10 | long-range and seed attacks; a pause-time deep reorg needs a peer's snapshot today | assumevalid's shape; 114.8 MB per snapshot measured | 3 + 3 | every tier: a fresh install cannot be bootstrapped onto a private DAG; node operator: disk for generations, no blocked executor after a deep reorg | the checkpoint in the release, the generation schedule | a cold node refuses a DAG missing the checkpoint; a 5,000-block reorg re-executes from a generation with no snapshot request |
|
||||
| 23 | Public-text corrections, one bundle: the prover's price as a formula with network hash as the input, never a number; the era draw and the reserve described as schedule changes against fixed datapaths, not unpredictability against a chip, with the chip's USD per MH/s-hour beside the honest cards'; F19's "day 19 to 20" is a v2 number (v3: a full window); funding.md section 4's dev-fee ceiling is 1 percent of the producer share (38,520 / 154,080 / 770,400); the dev fee is "default-on, switchable", not "optional"; "proofs at the cost of power" conditioned on hash; the three signalling thresholds in one sentence | L4 r3, r7; L2 r3; L3; L1 | each row cites its lane section | text | 1 to 3 each | holders and critics read claims that survive review | the litepaper, the customer brief, spec 5.7, funding.md, the ledger F19 | the site's forbidden-strings check and a reviewer's read |
|
||||
| 24 | Measure the FPGA lane on AWS F2 (one VU47P, HBM2, about USD 1.98 an hour) and replace the ceiling row | L2 r2 | the measured 2.4 G reads/s equals the JEDEC tFAW ceiling; the old 1.9x row rests on a 12 ns tFAW the JEDEC HBM2 table does not give (28 ns); the soft overlay reads 0.30x to 0.47x of the 5090 per watt | L2 5.1 | 8 to 10 plus USD 2 to 8 | none today; the public FPGA claim becomes a measured number | the overlay bench on F2 | reads per second per watt at 1 GiB; the row replaced |
|
||||
| 25 | Ember tune as the shipped default per card model, and the two fleet measurements every price rests on: the miner's hash loss while each tier proves, and a full 30 M-cycle shard beside the miner on 12 and 16 GB cards | L2 r7, L4 r8 | the 4070 at 3.65 uJ untuned and 2.57 tuned (-30 percent); the hybrid row is the only one that undercuts the market and rests on one 5090 measurement; the 4.7 M fixture is 16 percent of a full shard (linear scaling says 240 s on a 3060, outside the 120-s claim timeout) | L2 5.1; `utility.py hybrid_hash_loss` | 2 + 6 (fleet) | every NVIDIA tier gains 10 to 30 percent per joule; the 12 GB tier learns whether it can claim a full shard in time | the defaults table in the app from the fleet priors; eleven rows with both numbers in `prover-tiers-real-cards.md` | the rows land; the claim timeout is set from them |
|
||||
|
||||
Lanes 5, 6 and 8 landed after this table was ranked; their rows are in the lane files (network.md section 6, polish.md section 1, new-pow.md section 7) and in the one page above. The table itself is not re-ranked: rank 1 and rank 2 are in 0.3.16, rank 3 is in 0.3.16, rank 8 is done, rank 9's unwrap half is in 0.3.16, rank 23's text bundle is done.
|
||||
|
||||
### Not recommended, with the reason (as valuable as the list above)
|
||||
|
||||
| Item | Lanes | Why not |
|
||||
|---|---|---|
|
||||
| Prover attestations as a second finality leg | L1 r12, L3 r8 | provers are the miners (same vote keys), so no new party; proof coverage is 2.4 to 4.7 percent of blocks tonight with lag p99 62 s, so every lock would wait on proofs; revisit only after 99 percent of blocks are proven within 60 s for 7 days with 3 provers per block, and even then it adds about a minute of lock delay |
|
||||
| Time-locked (vesting) weight | L1 r13 | a bought key transfers vested weight, so the acquired-keys bound is unchanged; honest new cohorts wait longer |
|
||||
| Any automatic re-lock after an abrupt departure | L1 r14, L3 r7 | departure and partition are the same observation in one view; the decaying denominator produces 467 to 473 conflicting locks in a 360-minute 50/50 honest split, the hysteresis floor brings the 13.3 percent equivocator bound back (565 to 597 conflicts at 20 percent); the two-tier rule is a report, never a lock |
|
||||
| Mining is proving (the lottery's work as a proving step) | L8 scheme A, L7 3.10 | dead on bytes (2.9 MB of openings per block), on the verifier (32 to 40 ms against 10), and on sampleability (Ball et al. 2017, Ofelimos 2022); re-opens Aleo's fastest-prover-wins |
|
||||
| Proving others' chains as the main income | L7 3.11 | all of Ethereum L1's proving is about USD 36 a day at the Sep 2026 tracker cost against USD 13,700 a day of year-1 emission at USD 0.005; demand must grow 1,000x against a falling cost curve |
|
||||
| Burn-redirect audit bounties, a review escrow by 60 percent signal | L7 3.6, L4 task 3 | a dev fund with a veto, the switch spec 5.5 removed; the honest payer for a second audit is the entity's own provers |
|
||||
| Proof verification on a hardware wallet's secure element | L7 3.9 | a bn254 pairing on a Cortex-M-class element is seconds to minutes (approximate); do the companion verify |
|
||||
| A general compute market for unverifiable work (rendering, inference) | L7 3.12 | an escrow without a verifier is a trust-me payment with lower fees; the verifiable subsets are named and kept |
|
||||
|
||||
## 3. What a 51 percent attacker can and cannot do
|
||||
|
||||
The paper is `docs/analysis/51-percent.md` (lane 1). In one table, at the measured USD 11.7 per GH/s-hour:
|
||||
|
||||
| Hash share | What it buys | For how long | Cost at 1 GH/s / 1 TH/s of network | What it earns | Net |
|
||||
|---|---|---|---|---|---|
|
||||
| 20 percent | reorders only the last k = 18 blocks; nothing past a certificate | per attempt | subsidy forgone while withholding | its block share minus reds | loss |
|
||||
| 34 percent | wins the selected-chain race only inside 60 s (40 percent of the time); holds the veto (blocks every lock) after 20 days of mining at 51 percent, or 30 days at 52 percent of the network's hash | 20 to 30 days to acquire, then free to hold while silent | about USD 6k / 5.8 M to acquire (half earned back as subsidy); a pause then costs nothing | subsidy; nothing from the pause itself unless it double-spends at the 12-h depth during the pause (USD 146 to 146k) | the one line vote-or-burn (rank 5) and weight-gated fork choice (rank 4) close |
|
||||
| 45 to 51 percent | wins the selected-chain race over a 90-s hold 70 to 85 percent of the time (32 to 46 blocks at 1 bps); turns 26 to 28 percent of honest blocks red; lifts its weight share to about 56 percent, never 2/3 | the 63 to 93 s between a checkpoint block and its lock | USD 11.7 / 11,700 per hour of rented hash; the market could not supply a TH/s on 6 October (RunPod: 0 pods for 20 asks) | a quarter of honest subsidy while withholding; the pool capture of rank 1 (11,636 IGN/h) until the in-consensus verifier lands | loss on the chain; gain only through the pool, which rank 1 closes |
|
||||
| 67 percent of weight | locks alone | 2.03 x N of hash for 30 days: USD 17,100 per GH/s of network | | subsidy | at rental equilibrium about 33 percent of 30 days of subsidy net |
|
||||
| any share | a rule change | 95 percent of blue-block weight over the window, or the floor | 19 N for 24 h today (USD 534k at 100 GH/s); x7 under rank 3 | | |
|
||||
|
||||
What no share buys: a block the nodes do not re-execute (the native-execution veto), a state the aggregated proof chain does not commit to, a lock past a certificate under two thirds of weight, a parameter change without the signal.
|
||||
|
||||
The residual risks stated plainly: the first 20 days (no weight table yet); a pause after a sudden departure of a third of weight (30 days under v3 until rank 2 lands); the Sybil count of keys (X5's definition adopted, the measurement scheduled); the proving pool until rank 1; the 2/3-of-total rule under long churn (the window bound: a 50/50 honest partition locks on both sides from day 10).
|
||||
|
||||
## 4. Per lane: the three biggest findings
|
||||
|
||||
### Lane 1, consensus-security
|
||||
1. The lock bounds a majority; the k-cluster does not (45 to 51 percent wins a 90-s race 70 to 85 percent of the time; the lock at 63 to 93 s is the bound).
|
||||
2. The veto is cheap (20 days of 51 percent: USD 6k at 1 GH/s, 5.8 M at 1 TH/s, half earned back) and the pause is then free.
|
||||
3. The proving pool is capturable today (11,636 IGN/h at 51 percent) with a correct-statement fake-proof record; the only attack that earns more than it costs.
|
||||
|
||||
### Lane 2, algorithm
|
||||
1. Class v4's verifier measured on a 2022 server core: 4.90 / 5.06 / 8.23 ms (steady / cold / half-core); dr736 9.76 / 10.51 / 15.49, out; R0 is dr368.
|
||||
2. The f = 1 GDDR7 chip: 5.7x per joule against the 5090 at v3, 2.1x at v4 with k = 1; USD 0.00021 per MH/s-hour against USD 0.0117 rented; the FPGA soft overlay 0.30x to 0.47x per watt.
|
||||
3. The reserve and the era draw buy about nothing against a chip; the N ladder in the era draw at genesis is the lever, and lane 7's HBM4 column is reconciled with three named disagreements.
|
||||
|
||||
### Lane 3, finality-and-weight
|
||||
1. Tonight's pause: the 2/3 rule at checkpoint 6843 (53.1 percent of total after a 42.7 percent departure), then the frozen table (Q5) holding it for a window; locks formed without the hub; topology refuted.
|
||||
2. Only the departure announcement passes the line (0 conflicting locks everywhere, the pause under an hour); the decaying denominator, the hysteresis floor and the two-tier rule fail or are reports.
|
||||
3. Weight capture costs USD 8,424 x N x W/(1 - W) to rent for the window: the veto 0.52 N for 30 days (USD 4,300 per GH/s of network), a lock alone 2.03 N (USD 17,100); lock delay measured p50 0.86 to 1.26 s at 4 to 93 voters; the path breaks near 1,000 voters on per-vote verification.
|
||||
|
||||
### Lane 4, economy-and-utility
|
||||
1. The proving price is h/N: 100 to 300x Boundless at 1.16 GH/s, 0.2 to 0.4x at 100 GH/s beside the miner; the adopted floor overprices above USD 0.014 per IGN.
|
||||
2. Fees are not a security budget for a decade (USD 450 a day at launch, 4,200 in year 5, against 54,800 and 13,700 of emission); sustained honest hash costs USD 24.8 per GH/s-day against USD 281 rented, so the 20-day veto costs 11.8x the honest fleet at every price and year.
|
||||
3. The 80/20 survives every stress but a ten-day prover refusal, which strands 547,570 IGN a day of pool credit with no rule to return it.
|
||||
|
||||
### Lane 7, frontier
|
||||
1. HBM4 raises the stored-dataset chip's edge (lane 2 bounds the figure at 4.9x to 12x bare by tFAW); the N schedule belongs in the era draw at genesis.
|
||||
2. Vote weight is already a slashable, non-purchasable bond: work-stake for external jobs, with "no coin stake" written into the spec first.
|
||||
3. The consensus proof can be incremental: the weight table inside the recursive segment proof, one mergeset per segment; the first measurement is the in-guest BLS verify and colouring cycle counts.
|
||||
|
||||
### Lane 5, network
|
||||
1. Reds come from node cost, not topology: run A at 10 blocks a second on a star ran 77.6 percent red because the hub needed 61 to 345 ms per block; the propagation model predicts under 0.1 percent red in every bps x delay cell with an ideal hub.
|
||||
2. The controller reads blue work only, so a star or a withholder eases difficulty; the whole-DAG estimator holds the rate in every cell.
|
||||
3. The recommendation: 1 block a second for the testnet and launch; 10 behind three gates (per-block CPU under 50 ms on a laptop core at mergeset 248, finality constants in DAA seconds, vote aggregation). The experiment is `docs/analysis/block-rate-devnet2.md`.
|
||||
|
||||
### Lane 6, polish
|
||||
1. The pause had no cause on any surface: no `finality_reason` or frozen-table share in the node's report, the observer, the API, Ember or the hub (Q1, Q2, Q6); the 0.3.16 Ember carrier is on ember-tune.
|
||||
2. Every update was urgent once an activation height was behind the node (`fork_is_close`, Q4), and nothing refused an activation height at or below the live DAA; both fixed on ember-tune for 0.3.16.
|
||||
3. Unsigned installers on both desktops (Q3, the project lead's certificates) and the relay's security fixes still on fud-close (Q8).
|
||||
|
||||
### Lane 8, new-proof-of-work
|
||||
1. Scheme A (mining is proving) is a bound, not a design: 2.9 MB of openings per block or a 32 to 40 ms verify against the 10 ms gate; the useful fraction is 8 percent at 1 GH/s and 0.08 percent at 100 GH/s.
|
||||
2. Scheme B (tensor shadow) is free for the honest card and so nearly free for the chip (0.056 to 0.70 pJ per multiply-add against 11 pJ per ALU op); never as class content, kept as reserve R8.
|
||||
3. Scheme C (dataset from stored state) is the class v5 candidate: the 4090's rate and watts equal within noise (63.083 against 63.088 MH/s), build +1.4 ms, verifier +0.11 to 0.21 ms per unit, bit-exact on 1,024 items and 128 lanes.
|
||||
|
||||
## 5. What was not run, and why
|
||||
|
||||
| Item | Lane | Why |
|
||||
|---|---|---|
|
||||
| The in-guest BLS verify and GHOSTDAG colouring cycle counts (rank 12's first gate) | 7, 3 | no 24 GB card free tonight: PC 2 and the fleet were on class v4 and Devnet 2 |
|
||||
| The real 2019-class core (O-1.14) | 2 | the US laptop was not on the relay; the box's half-core proxy stands in |
|
||||
| The FPGA soft overlay on real HBM2 | 2 | no FPGA in the fleet; AWS F2 plan written |
|
||||
| The AMD watts at every N | 2 | the ADLX sampler row is owed on the runner's `--cards-off` mechanism (counter-asic-3-status 6a) |
|
||||
| A `cargo bench` of `fast_aggregate_verify` at 93, 1,000 and 8,192 keys | 3 | the BLS figures are arithmetic on blst's published timings, approximate |
|
||||
| The 10 bps mesh run A2 | 5 | dropped: run B (the 1 bps control) landed clean and the model attributes run A's reds to node cost, which a mesh does not change |
|
||||
| The full 30 M-cycle shard beside the miner on any tier; the hash loss while proving on Ampere and Ada | 4 | the fleet measured the 4.7 M fixture only |
|
||||
| Market prices for Taiko-class batch proving and Bonsai | 4 | not published; marked approximate |
|
||||
| The observed end of tonight's pause | 3 | expected at DAA 216,402, about 20:40Z; the timeline closes when the observer rows show the lock |
|
||||
|
||||
## 6. Rules and corrections for main
|
||||
|
||||
| # | Rule or correction | Lane | State |
|
||||
|---|---|---|---|
|
||||
| 1 | P21 is a priced economic hole until the in-consensus verifier lands; the 20 percent pool is not "paid to provers" without the caveat | 1 | relayed |
|
||||
| 2 | The one-day P2 signalling window is buyable for a day; seven consecutive windows | 1, 4 | relayed |
|
||||
| 3 | The DAG controller reads blue work only, so a withholder or a star eases difficulty 16 to 32 percent (run A's loop) | 1, 5 | relayed; lane 5 owns the fix |
|
||||
| 4 | No `unwrap` on a peer-driven path; the sibling list; a sync-request fuzz in `tools/ci` | 1 | relayed |
|
||||
| 5 | A standing box never leaves the live chain for an experiment; orchestrated departures over 10 percent staged under 10 percent an hour | 3 | relayed; the fleet library refusal is rank 8 |
|
||||
| 6 | Under rule v3 any sudden departure of a third of weight is a 30-day mainnet pause; the leave item is the one safe way to shorten it | 3 | relayed |
|
||||
| 7 | F19's "stalls until day 19 to 20" is a v2 number; under v3 a full window | 3 | relayed, ledger text owed |
|
||||
| 8 | "No coin stake; the only thing at stake is 30 days of public work" into spec 03/05 and the litepaper before any work-stake prototype | 7 | relayed; routed to the site-miner agent |
|
||||
| 9 | The litepaper's "proving: a second income" carries the USD 36 a day arithmetic | 7 | relayed; routed |
|
||||
| 10 | Ember's updater installs nothing while finality is paused | 7 | relayed; sent to the Ember agent for 0.3.16 |
|
||||
| 11 | The customer brief's "priced in dollars per proof" and the litepaper's "proofs at the cost of power" are conditioned on network hash | 4 | relayed |
|
||||
| 12 | The three signalling thresholds (60 / 90 / 95) in one sentence across spec 5.7, CLAUDE.md and the litepaper | 4 | relayed |
|
||||
| 13 | The spec is silent on pool credit nobody claims; today it is a burn nobody decided | 4 | relayed |
|
||||
| 14 | funding.md section 4 overstates the dev-fee ceiling by a quarter | 4 | relayed |
|
||||
| 15 | Any future miner-signalled execution parameter outside the consensus digest makes signal-then-defect a quiet state fork | 4 | relayed |
|
||||
| 16 | The era draw and the reserve are not unpredictability against a chip; the public text should say what they buy | 2 | relayed |
|
||||
350
docs/analysis/horizon/algorithm.md
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
# Horizon lane 2: the shipped hash and its class system, refined
|
||||
|
||||
6 October 2026, evening UK. Lane 2 (algorithm) of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon`, branch `horizon` on master 3f4f719. Scope: the shipped lottery hash and its classes (v2, v3 live, the v4 candidate `mx8+sh256x27`, the reserve R0 to R8, the era draw, the dataset schedule). No new puzzle is proposed here (lane 8 owns that). Every chip figure is arithmetic on cited figures and is approximate; every GPU figure names its bench-log entry or analysis file; the one new measurement is the verifier proxy on igneum-build-1 (section 2).
|
||||
|
||||
What was read, in full unless marked: `docs/spec/01-lottery-hash.md`, `docs/spec/04-seeds-and-vdf.md`, `docs/analysis/chip-model-v3.md`, `docs/analysis/asic-resistance-history.md`, `docs/analysis/latency-shadow-2026-10-06.md`, `docs/analysis/m16-recompute-attacker-2026-10-05.md`, `docs/analysis/sram-mirror.md`, `docs/analysis/int8-matrix-family.md`, `docs/analysis/weak-program-census-2026-10-03.md` (section 1), `docs/analysis/scratch-soundness.md` (section 0), `docs/analysis/card-lifetime-2026-10-05.md`, `docs/plans/counter-asic-3-status.md`, `docs/plans/counter-asic-3-reserve.md`, `docs/plans/counter-asic-3-derivation.md`, `docs/plans/counter-asic-2.md`, `docs/plans/epoch-length.md` (sections 11 and 12), `docs/plans/era-layout.md`, `docs/plans/mixer-x4.md` (sections 6.2 to 6.5), `docs/plans/read-width.md`, `docs/fud-ledger.md` (M1 to M31 headings; M9, M16, M17, M18 in full; M28 heading), `docs/bench-log.md` (entries "Counter ASIC 2.0, the numbers", "the 9070 XT on the eGPU", the 6 October item 8, item 6 and gate-run entries, "Rental cost of hash, 6 October 2026"), `igneum-pow/src/generator.rs` (the class constants, `ShadowClass`, `LoadClass::{V2, MX4, MX8, DR736}`), `igneum-pow/src/memhard.rs` (`Shape`, the growth rule, `round_key`, `derive_items_mask`), `igneum-pow/src/main.rs` (`bench`), `docs/analysis/prover-tiers-real-cards.md` (the 11 rented cards), and `/Users/joshm/Projects/igneum-wt-gpu-fleet/docs/analysis/block-rate-devnet2.md` (RUN_A and RUN_B are still placeholders at 21:30 UK; nothing from it is used).
|
||||
|
||||
## 1. The six questions and the one-line answers
|
||||
|
||||
| # | Question | Answer in one line |
|
||||
|---|---|---|
|
||||
| 1 | Chip model on the 6 October numbers; the FPGA lane | The stored-dataset chip (f = 1, GDDR7) reads 5.7x per joule against the 5090 bench row and 1.7x against the M5 Max at class v3; at class v4 and k = 1 those fall to 2.1x and 0.9x. Per dollar it is 56x under rented hash and 5.4x under an owned 5090 per MH/s-hour. The FPGA soft overlay tightens to 0.30x to 0.47x per watt: the measured 2.4 G reads/s equals the JEDEC tFAW ceiling of a 2-stack HBM2 part, so the 1.9x bank-bound row is unreachable on any FPGA that exists. AWS F2 at USD 1.98 an hour carries the exact HBM2 subsystem and can measure it |
|
||||
| 2 | Reserve R0 to R8 | Every reserve family together costs a chip about 8 to 14 adders per lane, about USD 4 of N5 silicon on a 14,000-lane array; none moves the per-joule edge by over 10 percent. The reserve's value is obsolescence of a datapath taped out against class v3, and since the order is public that value is zero against a chip that ships with every block. Recommended order: R0 derive at dr368 (dr736 fails the gate proxy), R1 shfla, R2 perm, R3 popc and clz, R4 bfe, R5 shifts, R6 sel, R7 andn, R8 mm8 at era 8 by the rule, no exception |
|
||||
| 3 | A class v5 from the shadow | Option (i) is void: the v4 shadow already consumes loaded data (its registers hold the dataset words of the iteration), so a chip precomputes nothing today. Option (ii), a shuffle-heavy shadow mix, raises the attacker's k floor from about 0.32 to 0.46 (approx), not to 1. Option (iii): the one N every owned card holds within 5 percent is 130,000 counted ops (the M5 Max's point); it buys 2.1x to 1.7x against the 5090 at k = 1 for 4.8 percent of the Mac's rate; the verifier at 130,000 is 4.9 ms on the box proxy and 8.5 ms on the half-core proxy, inside the gate |
|
||||
| 4 | The era draw's randomness | Forging the certified checkpoint the era VDF reads costs 20 days of 100 percent hash: USD 5,600 at 1 GH/s, USD 5.6M at 1 TH/s rented, and buys one draw of a space whose spread is 0.8 to 3.2 percent of hash rate per card and 0 percent for a chip. Re-rolling by withholding needs a 1,800x faster VDF. The draw buys nothing against a chip; the public reserve order means a chip is taped out with every block. What would cost a chip is work (N) and the honest card's own watts, not unpredictability |
|
||||
| 5 | The 2019-class verifier | Measured proxy tonight: a Zen 4 core at 3.8 GHz with server DRAM reads 2.0x to 2.2x the M5 Max core (v4 4.90 ms steady, 5.06 cold; dr736 9.76 steady, 10.51 cold: FAIL); with both SMT siblings busy (the pessimistic bracket) v4 8.23 ms, dr368 8.16, dr736 15.5. The 2.5x rule holds within 15 percent. The gate protects a node at 1 to 10 percent of one core per block rate, an 18-minute IBD, and a header-flood cost of 100 bogus headers per second per core |
|
||||
| 6 | Dataset growth to 2030 | Hold option (b): 2 GiB at genesis, 4 GiB at year 4, 8 GiB at year 12. The 8 GB tier (26.7 percent of Steam today, about 0 by 2030 on the trend) mines to year 12 anyway; the 12 GB tier loses mine-and-prove compressed at the year-4 step and the 8 GB tier loses core-only at genesis, and both are the prover's footprint, not the dataset's. Growth is for the SRAM reticle (1.6 GiB per reticle at N5) and GPU L2, not for the HBM chip |
|
||||
|
||||
## 2. Method
|
||||
|
||||
| Item | What was done | Where |
|
||||
|---|---|---|
|
||||
| The model | One Python script, every table in this file; inputs listed with source and label | `sim/horizon/algorithm/model.py`, `README.md` beside it |
|
||||
| Verifier proxy | `igneum-pow` built on igneum-build-1 through `tools/build-remote.sh` from the crate directory (`IGNEUM_AGENT=horizon`, build slot build-0, 10 s wall, sccache miss 1, artefact 991,352 bytes, sha256 6d2867...); `igneum-pow bench --seed igneum-genesis --day 2026-10-03 --class <c> --warps 50` for v2, mx8, mx8+sh256x27, dr368, dr736 under `nice -n 19 taskset -c 40` (one core), then the same on cores 40 and 88 at once (the two SMT siblings of one physical core: `thread_siblings_list` 40,88); load average 1.3 before, 2.3 after; `scaling_cur_freq` read 3,799,885 kHz during a run (the governor is schedutil with `scaling_max_freq` 2,750,000 but the core boosted to 3.8 GHz; `cpupower` needs root, so no fixed low clock was possible); the cache fill 361 ms on that core | section 5.5 |
|
||||
| Not run | A Mac packbench ladder for v5 (the data-dependent and shuffle-heavy shadow variants do not exist as kernel text, so nothing could be measured; the plan is in 5.3); any PC job; any FPGA | section 7 |
|
||||
| External figures | Steam Hardware Survey September 2026 (store.steampowered.com/hwsurvey, read 6 October 2026); ICCAD 2021 "Demystifying the Characteristics of High Bandwidth Memory for Real-Time Systems" Table I (JEDEC HBM2 timings in cycles at 2,133 MT/s, text extracted with pdftotext); Intel HBM2 IP user guide (16 AXI pseudo-channel ports per stack); AWS F2 pricing (f2.6xlarge USD 1.98 an hour on-demand, USD 0.66 spot, one VU47P with 16 GB HBM2) | section 5.1 |
|
||||
|
||||
## 3. Evidence
|
||||
|
||||
### 3.1 Honest cards, measured (class v3 unless said)
|
||||
|
||||
| Card (tier) | MH/s | W | uJ per hash | Source |
|
||||
|---|---|---|---|---|
|
||||
| RTX 5090, PC 2 bench, 431 W cap, the control | 132.2 | 350 | 2.65 | `latency-shadow-2026-10-06.md` s5; bench-log item 8 |
|
||||
| RTX 5090, PC 1 app, Ember run 5 | 127.4 | 310 | 2.43 | `counter-asic-3-status.md` s7 item 1 |
|
||||
| RTX 5090, app 5 October | 124 | 290 | 2.34 | `miner-eff` record, cited in latency-shadow s3 |
|
||||
| RTX 5090, rented Vast pod, untuned | 98.5 | 258 | 2.62 | `prover-tiers-real-cards.md` |
|
||||
| RTX 4090, rented | 52.3 | 183 | 3.50 | same |
|
||||
| RTX 3090, rented | 37.8 | 229 | 6.05 | same |
|
||||
| RTX A5000, rented | 47.7 | 223 | 4.67 | same |
|
||||
| RTX 4070, PC 1, 1,860 MHz lock, 160 W cap | 30.95 | 79.5 | 2.57 | status item 8, 4070 rows |
|
||||
| RTX 4070, rented | 25.0 | 91 | 3.65 | prover-tiers |
|
||||
| RTX 5070, rented | 41.9 | 137 | 3.27 | prover-tiers |
|
||||
| RTX 3060, rented | 23.8 | 104 | 4.36 | prover-tiers |
|
||||
| RTX 3080, rented | 40.8 | 205 | 5.02 | prover-tiers |
|
||||
| RTX 4060 Ti 16 GB, rented | 17.6 | 72 | 4.11 | prover-tiers |
|
||||
| RTX 4060 Ti 8 GB, rented | 19.1 | 73 | 3.81 | prover-tiers |
|
||||
| RTX 4060, rented | 17.1 | no reading (0.0 W logged) | n/a | prover-tiers |
|
||||
| RX 9070 XT, PC 1 | 18.9 | 199 to 203 | 10.6 | bench-log 9070 XT telemetry; status 3a |
|
||||
| Apple M5 Max, GPU + DRAM channels | 27.08 | 21.0 | 0.78 | latency-shadow s3 |
|
||||
| Apple M5 Max, package (approx) | 27.08 | 38 | 1.40 | `ember-tune.md`, approximate |
|
||||
|
||||
At class v4 (`mx8+sh256x27`, measured): the 5090 131.95 MH/s at 431 W (3.27 uJ, the cap binds), the M5 Max 26.67 at 37.2 W (1.39), the 4070 31.08 at 109 W (3.51), the 9070 XT 19.29 at owed watts. Every other card's v4 energy below is modelled as the card's marginal ALU energy times N (NVIDIA 11 pJ per counted op measured on the 5090, Apple 6.9 measured on the M5 Max, AMD taken as NVIDIA's, approximate).
|
||||
|
||||
### 3.2 Chips (model, approximate; `chip-model-v3.md` s5.4, latency-shadow s6)
|
||||
|
||||
| Chip class | MH/s | W at v3 | uJ, v3 | uJ at v4, k = 0.3 / 0.5 / 1 / 1.5 | $ silicon + memory, v3 / v4 | $ per MH/s |
|
||||
|---|---|---|---|---|---|---|
|
||||
| f = 0 on-die recompute, 256 MiB SRAM, x8 | 41.7 | 53.7 | 1.29 | 1.62 / 1.84 / 2.39 / 2.94 | 700 / 740 | 16.8 |
|
||||
| f = 1 stored dataset, GDDR7, 16 devices | 166.4 | 77.6 | 0.466 | 0.80 / 1.02 / 1.57 / 2.12 | 470 / 510 | 2.8 |
|
||||
| f = 1, HBM3 one stack | 83.6 | 26.8 | 0.321 | 0.65 / 0.87 / 1.42 / 1.97 | 550 / 590 | 6.6 |
|
||||
| f = 1, HBM3 eight stacks | 666 | 174 | 0.262 | 0.59 / 0.81 / 1.36 / 1.91 | 2,650 / 2,690 | 4.0 |
|
||||
|
||||
`k` is the chip core's energy per counted op over the 5090's measured 11 pJ. The f = 0 chip's measured stand-in (M16 round 2, the inline kernel inside the 5090's L2) ran 33.9 MH/s at 431 W: 0.256x per chip and 5x worse per joule than honest, so the f = 0 row above is the model's ceiling for that class, not a measurement.
|
||||
|
||||
### 3.3 The verifier proxy, measured tonight (igneum-build-1, EPYC 9454P, one core at 3.8 GHz, nice 19)
|
||||
|
||||
| Class | M5 Max core, quiet | 2.5x rule (approx) | Box one core, steady / cold | Box over Mac | Box half-core (SMT sibling loaded) | 10 ms gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| v2 | 0.60 | 1.5 | 1.13 / 1.30 | 1.88x | not run | pass |
|
||||
| mx8 (class v3) | 2.06 | 5.2 | 4.52 / 4.67 | 2.19x | 7.56 | pass |
|
||||
| mx8+sh256x27 (class v4) | 2.33 | 5.8 | 4.90 / 5.06 | 2.10x | 8.23 | pass |
|
||||
| dr368 | 2.69 | 6.7 | 4.72 / 5.32 | 1.75x | 8.16 | pass, thin on the half-core |
|
||||
| dr736 | 4.88 | 12.2 | 9.76 / 10.51 | 2.00x | 15.49 | FAIL (cold run over 10 ms on one core; 15.5 on the half-core) |
|
||||
|
||||
Raw lines (the Mac figures are `counter-asic-3-derivation.md` 5.1 and the gate-run entry): box one core `CPU verify: 1.128 / 4.516 / 4.901 / 4.716 / 9.763 ms per 32-lane warp, avg of 50`; cold `warp base 0: 1.305 / 4.670 / 5.061 / 5.324 / 10.509 ms`; half-core `7.562 / 8.234 / 8.162 / 15.491` (cpu 40) and `7.556 / 8.227 / 8.168 / 15.479` (cpu 88); every lane-0 and lane-31 hash equal to the Mac's vectors (`42246ba99fc58e4f`, `19b56348bc85304d`, the dr736 `e23d389f3eea0c83`). Cache fill 360.5 to 364.7 ms on the box core against 175 to 181 on the M5 Max.
|
||||
|
||||
### 3.4 Installed base (Steam Hardware Survey, September 2026, cited)
|
||||
|
||||
| VRAM | Share | Trend (cited: 8 GB 35.03 percent in August 2025, 33.66 in September 2025; 12 GB 19.30 in August 2025) |
|
||||
|---|---|---|
|
||||
| 512 MB to 4 GB | 14.06% | falling |
|
||||
| 6 GB | 5.12% | falling |
|
||||
| 8 GB | 26.71% | about -7 points a year |
|
||||
| 10 to 11 GB | 2.74% | flat |
|
||||
| 12 GB | 13.06% | about -6 points a year |
|
||||
| 16 GB | 27.21% | rising; overtook 8 GB in August 2026 |
|
||||
| 20 to 24 GB | 7.93% | rising slowly |
|
||||
| 32 GB | 1.41% | rising slowly |
|
||||
| 64 GB | 0.50% | new row |
|
||||
|
||||
### 3.5 Rental and ownership cost (bench-log "Rental cost of hash, 6 October 2026", measured)
|
||||
|
||||
USD 0.0117 per MH/s-hour on RunPod community pods (1,748 MH/s for USD 20.44 an hour); the 8x 4090 rig USD 0.0129; a 5090 pod USD 0.41 to 0.74 an hour for 98 to 128 MH/s.
|
||||
|
||||
## 4. Model
|
||||
|
||||
| Formula | Inputs (label) |
|
||||
|---|---|
|
||||
| Energy per hash E = W / rate | measured watts and MH/s per card; chip W from `chip-model-v3.md` 5.4 (approx) |
|
||||
| Chip energy at class v4: E_v3 + N x 11 pJ x k | N = 100,000 counted ops (measured class v4), 11 pJ = the 5090's marginal per op (measured), k free |
|
||||
| Edge per joule = E_card / E_chip | both sides above |
|
||||
| Hourly cost per MH/s = price / (2 years x rate) + E x 3.6e9 x USD 0.10 per kWh | prices approximate (launch list from memory, labelled); chip $ from 5.4; electricity approx |
|
||||
| FPGA random-read ceiling = min(banks / tRC, channels x 4 / tFAW, channels / tRRD) | 2 stacks, 16 channels, 32 pseudo-channels, 16 half-banks each (approx); tRC 48 cycles, tFAW 30, tRRD 6 at 1,066 MHz (ICCAD 2021 Table I, JEDEC HBM2); latency 137.8 ns (Shuhai, measured) |
|
||||
| Reads in flight = rate x latency; per watt = rate / board W | U55C 115 to 150 W, U280 225 W (datasheets) |
|
||||
| Verifier add per warp = shadow instructions / 1,000 x slope | slope 3.2 us (M5 Max, measured), 7.0 us (box one core, this lane), 12.1 us (box half-core, this lane) |
|
||||
| Checkpoint forgery cost = network MH/s x 480 h x USD 0.0117 | 20 days of 100 percent hash to 2/3 weight (CLAUDE.md headline, from the finality sim) |
|
||||
| Tier fit at a dataset step: miner resident = dataset + 192 MiB; prover peak beside the miner from `prover-tiers-real-cards.md` scaled by the dataset's growth | usable VRAM 75 percent (mine-only), 98 percent headless (the prover rows) |
|
||||
|
||||
## 5. Results
|
||||
|
||||
### 5.1 Task 1: the chip model on the 6 October numbers, and the FPGA lane
|
||||
|
||||
Per joule and per dollar against every honest card (the full table with every card is the `chip` section of the model; the rows that decide things):
|
||||
|
||||
| Card (tier) | uJ v3 / v4 | f = 0 chip edge, v3 / v4 at k = 1 | f = 1 GDDR7 edge, v3 / v4 at k = 0.3 / 0.5 / 1 / 1.5 | f = 1 HBM3 one stack, v3 / v4 at k = 1 | $ per MH/s (card, approx) | USD per MH/s-hour owned |
|
||||
|---|---|---|---|---|---|---|
|
||||
| RTX 5090 bench (32 GB) | 2.65 / 3.27 | 2.1x / 1.4x | 5.7x / 4.1x / 3.2x / 2.1x / 1.5x | 8.3x / 2.3x | 15.1 | 0.00113 |
|
||||
| RTX 5090 app, Ember (32 GB) | 2.43 / 3.53* | 1.9x / 1.5x | 5.2x / 4.4x / 3.5x / 2.3x / 1.7x | 7.6x / 2.5x | 15.7 | 0.00114 |
|
||||
| RTX 4090 rented (24 GB) | 3.50 / 4.60* | 2.7x / 1.9x | 7.5x / 5.8x / 4.5x / 2.9x / 2.2x | 10.9x / 3.2x | 30.6 | 0.00210 |
|
||||
| RTX 3090 rented (24 GB) | 6.05 / 7.15* | 4.7x / 3.0x | 13.0x / 9.0x / 7.0x / 4.6x / 3.4x | 18.9x / 5.0x | 39.7 | 0.00287 |
|
||||
| RTX 4070 PC 1 tuned (12 GB) | 2.57 / 3.51 | 2.0x / 1.5x | 5.5x / 4.4x / 3.5x / 2.2x / 1.7x | 8.0x / 2.5x | 17.7 | 0.00127 |
|
||||
| RTX 5070 rented (12 GB) | 3.27 / 4.37* | 2.5x / 1.8x | 7.0x / 5.5x / 4.3x / 2.8x / 2.1x | 10.2x / 3.1x | 13.1 | 0.00108 |
|
||||
| RTX 3060 rented (12 GB) | 4.36 / 5.46* | 3.4x / 2.3x | 9.4x / 6.9x / 5.4x / 3.5x / 2.6x | 13.6x / 3.8x | 13.8 | 0.00123 |
|
||||
| RTX 4060 Ti 8 GB rented (8 GB) | 3.81 / 4.91* | 3.0x / 2.1x | 8.2x / 6.2x / 4.8x / 3.1x / 2.3x | 11.9x / 3.5x | 20.9 | 0.00157 |
|
||||
| RTX 4060 Ti 16 GB rented (16 GB) | 4.11 / 5.21* | 3.2x / 2.2x | 8.8x / 6.5x / 5.1x / 3.3x / 2.5x | 12.8x / 3.7x | 28.4 | 0.00203 |
|
||||
| RX 9070 XT (16 GB AMD) | 10.6 / 11.7* | 8.3x / 4.9x | 22.8x / 14.7x / 11.5x / 7.5x / 5.5x | 33.2x / 8.3x | 31.7 | 0.00287 |
|
||||
| Apple M5 Max, GPU + DRAM | 0.78 / 1.39 | 0.6x / 0.6x | 1.7x / 1.8x / 1.4x / 0.9x / 0.7x | 2.4x / 1.0x | 129 | 0.00745 |
|
||||
| Apple M5 Max, package (approx) | 1.40 / 2.02 | 1.1x / 0.9x | 3.0x / 2.5x / 2.0x / 1.3x / 1.0x | 4.4x / 1.4x | 129 | 0.00752 |
|
||||
|
||||
`*` modelled v4 energy. The chip's hourly cost per MH/s (two-year amortisation plus electricity, approx): f = 1 GDDR7 USD 0.00021, HBM3 one stack 0.00041, eight stacks 0.00025, f = 0 recompute 0.00109; an owned 5090 0.00113; rented hash 0.0117. So the stored-dataset chip undercuts rented hash 56x and an owned 5090 5.4x per MH/s-hour, and the recompute chip matches the 5090 exactly, which is why nobody builds it.
|
||||
|
||||
What changed against the 5 October record: the honest denominators moved (the 5090 is 2.34 to 2.65 uJ by where it is measured, not 2.40), the marginal ALU energy is measured at 11 pJ (not the model's 5.5), the M5 Max at 0.78 uJ is the honest best per joule by 3x, and the rented fleet shows the untuned mid-tier (3060, 3080, 3090, A5000, 4060 Ti) at 3.8 to 6.1 uJ, 1.5 to 2.3x worse than the 5090 bench row: against those cards the GDDR7 chip reads 8x to 13x per joule at v3 and 3.1x to 4.6x at v4 with k = 1. The per-tier reading: the Apple tier is already inside 2x of the GDDR7 chip with no shadow and crosses under 1x at v4 and k = 1; the 5090 and the tuned 4070 reach about 2.1x to 2.2x at v4 and k = 1; the untuned mid-tier and AMD stay at 3x to 7.5x, which Ember tuning (the 4070 rows: 3.65 to 2.57 uJ) closes by about 30 percent and nothing in the hash closes further.
|
||||
|
||||
The FPGA lane. The brief's soft-overlay range was 0.30x to 0.39x per watt measured-basis and 0.7x to 1.9x at an unmeasured bank-bound ceiling. The reads-in-flight model with the JEDEC HBM2 timings:
|
||||
|
||||
| Ceiling | Formula | G reads/s per card | Reads in flight at 137.8 ns | Per W at 115 / 150 / 225 W (M/s/W) | Against the 5090 per W (53.7 M at 326 W) |
|
||||
|---|---|---|---|---|---|
|
||||
| Measured, Shuhai U280 default mapping (FCCM 2020, Fig 7) | 32 pc x 75 M | 2.4 | 331 | 21 / 16 / 11 | 0.39x to 0.30x (U55C); 0.20x (U280) |
|
||||
| tFAW-bound (JEDEC HBM2, ICCAD 2021 Table I: 30 cycles at 1,066 MHz, 4 ACT per channel) | 16 ch x 4 / 28.1 ns | 2.3 | 313 | 20 / 15 / 10 | 0.37x to 0.28x; 0.19x |
|
||||
| tRRD-bound (6 cycles) | 16 ch / 5.6 ns | 2.8 | 392 | 25 / 19 / 13 | 0.46x to 0.35x; 0.24x |
|
||||
| Bank-bound, no activate window (the epoch-length 12.2 ceiling row) | 32 pc x 16 banks / 45 ns | 11.4 | 1,567 | 99 / 76 / 51 | 1.84x to 1.41x; 0.94x |
|
||||
| O'Connor's HBM2 activate figure as carried by chip-model-v3 5.3 | 16 ch x 8 / 12 ns | 10.7 | 1,470 | 93 / 71 / 47 | 1.73x to 1.32x; 0.88x |
|
||||
|
||||
The reading: Shuhai's measured 2.4 G/s equals the tFAW ceiling at JEDEC timings (2.3 G/s). The measured row was read on 6 October as a mapping artefact ("the paper's point is that this mapping is the wrong one for random access"); the activate window says it is the DRAM's own limit, which a bank-interleaved mapping does not lift because tFAW is enforced per channel by the die. The 11.4 G bank-bound row needs tFAW gone; O'Connor's 12 ns figure (which the chip model carries for HBM2 and HBM3) is 2.3x shorter than the JEDEC HBM2 cycle count tabled by ICCAD 2021, and the difference is the whole 0.7x to 1.9x row. Tightened range for a 2-stack HBM2 FPGA (U55C, U280, F2's VU47P): 2.3 to 2.9 G reads/s, 0.30x to 0.47x of the 5090 per watt, in the RX 9070 XT's class (2.4 to 2.7 G/s measured). HBM2e parts (Versal HBM, Agilex 7 M) raise the pin rate, not tFAW in nanoseconds (approx), so they sit in the same band; no FPGA with HBM3 exists as a product. Approximate throughout: the 16 half-banks per pseudo-channel, the 1,066 MHz reading of the ICCAD table, the board watts under load.
|
||||
|
||||
Can a rented FPGA hour measure it? Vast.ai lists no FPGAs (GPU marketplace only, checked 6 October 2026). AWS F2 (f2.6xlarge: one Virtex UltraScale+ HBM VU47P, 16 GB HBM2 in 2 stacks, 32 pseudo-channels, USD 1.98 an hour on-demand in us-east-1, USD 0.66 spot) carries the same HBM2 subsystem as the U55C and U280, so yes. The gate, written as a measurement plan:
|
||||
|
||||
| Step | What | Hours (agent) | Pass line |
|
||||
|---|---|---|---|
|
||||
| 1 | Port the chase kernel of `docs/benchmarks/repro.md` 2.2 to a Vitis HLS AXI master over the HBM IP: 1 GiB working set across all 32 pseudo-channels, N dependent 4-byte-reads-in-flight lanes (N = 256, 1,024, 4,096), the HBM IP's address map set to bank-interleaved (RAMA or the IP's "random access" option), a second variant with 32-byte reads | 4 to 6 | the kernel reports reads per second and the chain's checksum equal to the CPU's |
|
||||
| 2 | Build the AFI (the F2 shell flow; the Vivado licence rides with the instance), 2 to 4 hours of F2 time at USD 2 to 8 | 2 (mostly waiting) | an AFI that loads |
|
||||
| 3 | Run the ladder; read board power through `xbutil examine --report electrical` (or the F2 shell's sensors) at 1 Hz; take the mean over each run | 1 | reads per second and watts per rung |
|
||||
| 4 | Write the row into `epoch-length.md` 12.2 in place of the ceiling row | 1 | the public claim carries a measured FPGA number |
|
||||
| Gate | reads per second per watt at 1 GiB | | expected 15 to 25 M/s/W (0.3x to 0.5x of the 5090); the alarm line is 27 M/s/W (0.5x); over 54 M/s/W (1.0x) the FPGA lane becomes a Counter ASIC 4.0 item |
|
||||
|
||||
Consequences per tier of the FPGA finding: none today (no FPGA mines); if the measured row holds, a soft-overlay FPGA at USD 4,000 to 5,000 a card (approx) mines at an RX 9070 XT's rate per watt for 7x the price, so no home or rig tier is displaced by it; the per-program bitstream lane stays answered by layer 9.
|
||||
|
||||
### 5.2 Task 2: the reserve R0 to R8
|
||||
|
||||
| Slot | Family | Chip block it adds (approx area in 32-bit adders per lane, `counter-asic-3-reserve.md` s3) | Chip datapath energy per op, N5 floor (pJ, approx) | Honest step cost, Apple / NVIDIA / AMD (measured, ratio to the add-xor-rotate chain) | Verifier cost | Chip edge per joule it moves (model) | Cost per tier |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| R0 | derive (per-day item program) | a sequencer: 60 KB instruction store, 16-register file, ALU with multiplier and rotator; removes the 3x fixed-function credit of the f = 0 chip | n/a (it is the f = 0 chip's item cost: 9,992 ops per item) | hash rate 0 on all three vendors; daily build +7 ms Mac, 0 on the 5090 and 9070 XT; compile +0.75 s Mac, +1.1 s NVRTC, +2.0 s AMD per day (a once-a-day module is a requirement) | dr736 +2.8 ms per warp on the Mac, +5.2 on the box core, over the gate cold; dr368 +0.6 Mac, +0.2 box, 8.16 on the half-core | f = 0 chip: 0.92x to 0.34x to 0.43x per chip; per joule from 1.86x to about 0.7x to 0.9x at the same allowance (approx); f = 1 chips: 0 | pool verifier cores x2.4 at dr736, x1.3 at dr368; every miner tier 0 |
|
||||
| R1 | shfla (lane + delta) | 32-lane x 32-bit crossbar per warp, 32,768 mux bits, 3 to 6 adders per lane | 1.00 | 1.91 / 1.53 / 0.75 to 0.84 | one op per instruction, under 0.01 ms per warp at W_new = 4 | shadow floor +21 percent at 5 percent of instructions (approx); the GPU pays 1.5x the step too, so k unchanged | Apple under 1 percent of ALU time (argued), NVIDIA and AMD 0 |
|
||||
| R2 | perm (byte permute) | 4x4 byte crossbar, 128 mux bits, 1 to 2 adders | 0.10 | 1.13 emulated / 1.30 / 1.73 to 1.93 emulated | negligible | under 1 percent | Apple and AMD emulate at 1.1x to 1.9x per op: under 1 percent at 4 points |
|
||||
| R3 | popc and clz | popcount tree and priority encoder, 1.5 to 3 adders | 0.10 | 0.87 and 1.01 / 1.50 and 1.63 / 0.91 to 1.01 and 1.19 to 1.30 | negligible | under 1 percent | 0 |
|
||||
| R4 | bfe | mask generator on the shifter, 0.3 adders | 0.06 | 0.77 / 1.54 / 1.00 to 1.09 | negligible | 0 | 0 |
|
||||
| R5 | shl, shr | barrel shifter beside the rotator, 0.2 adders | 0.06 | 0.85 and 0.86 / 1.27 and 1.28 / 1.00 to 1.02 and 0.91 to 1.02 | negligible | 0 | 0 |
|
||||
| R6 | sel | 32 muxes, 0.3 adders | 0.03 | 0.76 / 1.32 / 0.90 to 1.01 | negligible | 0 | 0 |
|
||||
| R7 | andn | 32 inverters, under 0.1 adders | 0.02 | 0.75 / 1.26 / 0.91 to 1.01 | negligible | 0 | 0 |
|
||||
| R8 | mm8 | 8x8x16 u8 MAC tile per warp, about 100 adders per lane; licensable IP at every node | 1.60 | owed (Metal 4 matmul2d) / 2.43 / 1.68 to 1.83 native, exactness unverified | 1,024 MACs per instruction per unit, about 10 us per warp at W_new = 4 | shadow floor +37 percent at 5 percent of instructions (approx); the GPU pays 2.4x the step: k unchanged or worse for the GPU | Apple pays the library path (owed); NVIDIA and AMD native |
|
||||
|
||||
The ordering argument. Against the f = 1 chip, which is the chip anyone builds, every family R1 to R8 moves the per-joule edge by under 10 percent, because the chip's cost at class v4 is N x k x 11 pJ and a family changes the per-op energy of 4 points in 79 of the shadow mix. Against the f = 0 chip only R0 matters (it is the item cost). So the reserve's function is not per-joule resistance; it is to make a datapath taped out against class v3's eleven families wrong at the first unlock. A chip maker who reads this document tapes out every block from day one: R1 to R7 together are about 8 to 14 adders per lane (a lane with a 32x32 multiplier is about 35), about 11 mm^2 on a 14,000-lane N5 array, about USD 4 of silicon (sram-mirror.md's USD 0.36 per mm^2, approx); R8 is a licensed tile. The era schedule therefore buys nothing against a chip and the order can be set by honest cost alone: the largest new structure first while every vendor's measured step cost is in (shfla: AMD measured cheap at 0.75 to 0.84, which the reserve document said was the one number that could move it to R1), the emulated families next, mm8 last at era 8 by the rule (no era-4 exception: mm8 removes no adversary class, `epoch-length.md` 12.4). R0 enters at dr368, not dr736: the box proxy puts dr736 over the gate on a cold run (10.5 ms) and at 15.5 ms on the half-core; dr368 reads 4.7 and 8.2.
|
||||
|
||||
Recommended text change to `counter-asic-3-reserve.md` section 5: R1 shfla, R2 perm, R3 popc and clz, R4 to R7 unchanged, R8 mm8 at era 8; R0 at `derive_len = 368` with 736 behind the O-1.14 measurement. The era schedule "family n at era n" stands; what it costs per tier at each unlock is the step-cost table above (Apple under 1 percent of ALU time per family, NVIDIA and AMD nothing measurable), and what it buys is stated honestly in the public text (section 6, proposal 4).
|
||||
|
||||
### 5.3 Task 3: a class v5 from the shadow
|
||||
|
||||
The three options, each priced:
|
||||
|
||||
(i) Shadow ops that depend on the loaded data. The v4 shadow block runs at the end of every iteration on the eight lane registers, and those registers hold the words the iteration's 16 loads XORed in (`generator.rs`: the block is executed after instruction 63 with the iteration's `sel`; `verify.rs` runs it with the same `step`). So every shadow op already consumes loaded data, and the next iteration's load addresses depend on the shadow's outputs. A chip cannot precompute any of it; what it can do is what the GPU does: overlap one lane's shadow with another lane's reads in flight. Option (i) therefore moves k by 0. A variant that draws the shadow's immediates from loaded words is M18's one-bit select at 32 bits: a chip wires the operand, and it is not a defence. Verdict: void; no measurement needed.
|
||||
|
||||
(ii) Shadow work that exercises GPU structures a chip lacks. Bank-conflict timing is not a value and cannot enter a bit-exact function. Register-file width (8 registers today) can be widened in the shadow to 16 or 32 (ProgPoW used 32): a chip's register file per lane in flight grows from 32 B to 128 B, 1.8 MB on a 14,000-lane array, trivial. Warp shuffles are the structure that costs: the xor-mask shuffle needs a 5-stage butterfly per warp (5,120 mux bits), the indexed shuffle a full crossbar (32,768). The chip datapath floor per op (N5, approx: add 0.06 pJ, mul 0.52, butterfly 0.30, crossbar 1.00, mm8 tile 1.60) against the GPU's measured step costs:
|
||||
|
||||
| Shadow op mix | Chip floor per op (pJ, approx) | k floor after the 2x pipeline and 8x register and wire overhead of latency-shadow s6 (approx) | GPU cost of the same mix on the 5090 (step ratios, measured) |
|
||||
|---|---|---|---|
|
||||
| Today's weights (add 32, mul 22, rot 13, shfl 8 of 75) | 0.221 | 0.32 | 1.0x to 1.5x per op |
|
||||
| Shuffle-heavy (shfl 14, shfla 8, the rest scaled) | 0.315 | 0.46 | the shuffles cost the 5090 1.49x to 1.53x the chain step, so its own energy per op rises about 10 percent (approx) |
|
||||
| With mm8 at 8 points | about 0.45 | about 0.65 | mm8 costs the 5090 2.43x the step |
|
||||
|
||||
So a shuffle-heavy shadow raises the attacker's claimed floor from about 0.3 to about 0.5 and costs the GPU about 10 percent more energy per shadow op; it does not reach k = 1. What a chip would need to add: the 32-lane crossbar per warp (R1's structure), nothing else new. The structure argument is bounded: a wide-SIMD array with a crossbar per 32 lanes is still a fixed datapath with no scheduler, and that is where the 0.3 came from.
|
||||
|
||||
(iii) One N for every card. Per-card bind points at the 5 percent rule (measured): M5 Max about 130,000 counted ops (interpolated between 102,100 at -1.5 percent and 150,800 at -7.3), RTX 5090 at its 431 W cap about 210,000 (199,600 at -2.7, 330,700 at -34.7), RTX 4070 at its 160 W cap about 250,000 (199,600 at +0.4, 330,700 at -12.3, approx), RX 9070 XT over 331,000 (holds at every rung; budget about 650,000). The consensus N is the minimum: 130,000, set by the Mac. The unmeasured rented cards by ALU budget (approx, cores x clock): 3060 about 270,000, 4060 about 440,000, 3080 about 360,000; their power caps are unmeasured and on NVIDIA the cap binds before the budget, so these are not pass marks. The ladder at every N the lane can compute:
|
||||
|
||||
| N counted ops | 5090 uJ (rate delta) | M5 Max uJ (delta) | 4070 uJ (delta) | f = 1 GDDR7 chip uJ at k = 0.3 / 0.5 / 1 / 1.5 | Edge over the 5090 | Edge over the M5 Max | Edge over the 4070 | Verifier add per warp: M5 Max / box core / box half-core (ms) |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 930 (class v3) | 2.65 | 0.78 | 2.57 | 0.47 / 0.47 / 0.47 / 0.47 | 5.7x | 1.66x | 5.5x | 0 |
|
||||
| 102,100 (class v4) | 3.27 (-0.2%) | 1.39 (-1.5%) | 3.51 (+0.4%) | 0.80 / 1.02 / 1.58 / 2.14 | 4.1x / 3.2x / 2.1x / 1.5x | 1.74x / 1.36x / 0.88x / 0.65x | 4.4x / 3.4x / 2.2x / 1.6x | 0.18 / 0.39 / 0.67 |
|
||||
| 130,000 (the one-N candidate) | 3.27 (-0.3%) | 1.43 (-4.8%) | 3.78 (+0.4%) | 0.89 / 1.18 / 1.89 / 2.60 | 3.7x / 2.8x / 1.7x / 1.3x | 1.61x / 1.22x / 0.76x / 0.55x | 4.2x / 3.2x / 2.0x / 1.5x | 0.23 / 0.49 / 0.85 |
|
||||
| 150,800 | 3.27 (-0.3%) | 1.46 (-7.3%) | 3.98 (+0.4%) | 0.96 / 1.29 / 2.11 / 2.94 | 3.4x / 2.5x / 1.5x / 1.1x | 1.52x / 1.13x / 0.69x / 0.50x | 4.1x / 3.1x / 1.9x / 1.4x | 0.26 / 0.57 / 0.99 |
|
||||
| 199,600 | 3.35 (-2.7%) | 1.58 (-10.5%) | 4.45 (+0.4%) | 1.12 / 1.56 / 2.65 / 3.74 | 3.0x / 2.1x / 1.3x / 0.9x | 1.40x / 1.01x / 0.59x / 0.42x | 4.0x / 2.9x / 1.7x / 1.2x | 0.35 / 0.76 / 1.31 |
|
||||
| 330,700 | 4.99 (-34.7%) | 1.87 (-21.0%) | 5.89 (-12.3%) | 1.55 / 2.28 / 4.09 / 5.91 | 3.2x / 2.2x / 1.2x / 0.8x | 1.21x / 0.82x / 0.46x / 0.32x | 3.8x / 2.6x / 1.4x / 1.0x | 0.58 / 1.26 / 2.18 |
|
||||
|
||||
Per-tier watts at N = 130,000 (measured rungs interpolated): the M5 Max 37 W GPU plus DRAM (from 21), the 5090 431 W (its cap, from 350), the 4070 about 118 W (from 79.5), the 9070 XT owed; a 5090 rig pays about 23 percent more electricity for 0.3 percent less rate, an Apple miner 1.8x the GPU watts for 4.8 percent less rate, a pool user nothing, every verifier tier +0.23 to +0.85 ms per warp. The verifier at 130,000 on the half-core proxy is 8.5 ms (8.23 + 0.85 - 0.67), inside the gate with 1.5 ms spare; the gate does not bind N before the Mac does.
|
||||
|
||||
#### 5.3a The reconciled N ladder (this lane's measured cards, lane 7's HBM4 inputs; `--section ladder`)
|
||||
|
||||
Lane 7 (`docs/analysis/horizon/frontier.md` 2.3, `sim/horizon/frontier/frontier_model.py` model 1.4) adds HBM4: JEDEC JESD270-4 doubles channels per stack (16 to 32, cited), so its one-stack ceiling is taken as 2 x 10.7 = 21.4 G reads/s at 1.0 nJ per read (approximate, unsourced), 5 W static, 10 W controller: 167 MH/s at 0.218 uJ bare. The table below carries that chip beside the three of chip-model-v3 5.4, every chip at N = bare + (N - 930) x 11 pJ x k, and every honest card at its measured rung.
|
||||
|
||||
| N counted ops | 5090 uJ, W (rate delta) | M5 Max uJ, W (delta) | 4070 uJ, W (delta) | 9070 XT rate delta (W owed) | 12 GB and 8 GB rented cards | Chip edge over the 5090 per joule at k = 0.5 / 1 / 1.5: GDDR7 | HBM3 one stack | HBM3 eight stacks | HBM4 one stack | Verifier ms per warp: M5 Max core / 2019-class by the 2.5x rule / measured half-core proxy | Card that binds first (5 percent rule) |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 930 (class v3) | 2.65, 350 W (0) | 0.78, 21 W (0) | 2.57, 80 W (0) | 0 | measured at v3 only: 5070 3.27 uJ, 3060 4.36, 4070 untuned 3.65, 4060 Ti 8 GB 3.81 | 5.7x | 8.3x | 10.1x | 12.2x | 2.06 / 5.2 / 7.56 | none |
|
||||
| 49,700 | 3.21, 425 W (+0.1%) | 1.16, 31 W (-1.2%) | 3.01, 94 W (+0.4%) | +2.1% | not measured at any N | 4.4x / 3.2x / 2.5x | 5.5x / 3.7x / 2.9x | 6.1x / 4.0x / 3.0x | 6.6x / 4.3x / 3.1x | 2.15 / 5.4 / 7.88 | none |
|
||||
| 102,100 (class v4) | 3.27, 431 W (-0.2%) | 1.39, 37 W (-1.5%) | 3.51, 109 W (+0.4%) | +2.0% | not measured | 3.2x / 2.1x / 1.5x | 3.7x / 2.3x / 1.6x | 4.0x / 2.4x / 1.7x | 4.2x / 2.5x / 1.7x | 2.24 / 5.6 / 8.23 | none (M5 Max -1.5%) |
|
||||
| 130,000 | 3.27, 431 W (-0.3%) | 1.43, 37 W (-4.8%) | 3.78, 117 W (+0.4%) | +1.7% | not measured | 2.8x / 1.7x / 1.3x | 3.2x / 1.9x / 1.3x | 3.4x / 1.9x / 1.4x | 3.5x / 2.0x / 1.4x | 2.29 / 5.7 / 8.41 | M5 Max at its 5 percent point |
|
||||
| 150,800 | 3.27, 431 W (-0.3%) | 1.46, 37 W (-7.3%) | 3.98, 124 W (+0.4%) | +1.5% | not measured | 2.5x / 1.5x / 1.1x | 2.9x / 1.7x / 1.2x | 3.0x / 1.7x / 1.2x | 3.1x / 1.8x / 1.2x | 2.32 / 5.8 / 8.55 | M5 Max (-7.3%) |
|
||||
| 199,600 | 3.35, 431 W (-2.7%) | 1.58, 38 W (-10.5%) | 4.45, 138 W (+0.4%) | +1.0% | not measured | 2.1x / 1.3x / 0.9x | 2.4x / 1.3x / 0.9x | 2.5x / 1.4x / 0.9x | 2.6x / 1.4x / 1.0x | 2.41 / 6.0 / 8.87 | M5 Max (-10%), 5090 (-2.7%) |
|
||||
| 330,700 | 4.99, 431 W (-34.7%) | 1.87, 40 W (-21.0%) | 5.89, 160 W (-12.3%) | +3.6% | not measured | 2.2x / 1.2x / 0.8x | 2.3x / 1.3x / 0.9x | 2.4x / 1.3x / 0.9x | 2.5x / 1.3x / 0.9x | 2.64 / 6.6 / 9.74 | 5090 (-35%), M5 Max (-21%), 4070 (-12%) |
|
||||
|
||||
Sources per column: 5090 and M5 Max rungs `latency-shadow-2026-10-06.md` s3 and s5; 4070 and 9070 XT rungs `counter-asic-3-status.md` item 8 (the 9070 XT's watts owed: the ADLX sampler parsed 0 samples); the rented cards `prover-tiers-real-cards.md` (class v3 only); chip bare energies chip-model-v3 5.4 and lane 7 model 1.4; the 11 pJ unit latency-shadow s5; the verifier slopes 3.2 us per 1,000 shadow instructions (Mac, measured) and 12.1 us (box half-core, this lane); the 130,000 row interpolated. Every chip cell is approximate.
|
||||
|
||||
Disagreements with lane 7's model 1.4, named: (a) its honest card at N is the linear 326-to-575 W model of chip-model-v3 5.7 (2.95 uJ at N = 100,000, 3.50 at 200,000, 4.22 at 330,000); the measured 5090 under its 431 W cap reads 3.27, 3.35 and 4.99 uJ with -0.2, -2.7 and -34.7 percent of rate (the cap binds from 102,100 ops; `power.min_limit` is 400 W, so no cap below the one measured exists on a 5090). (b) Its shadow core is 150 W fixed at the 5090's 136 MH/s; on a 167 MH/s HBM4 chip that under-counts the core by 1.23x. Energy per hash is N x 11 pJ x k whatever the chip's rate, so HBM4 at N = 100,000 and k = 1 is 1.33 uJ and the edge 2.5x, not 1.11 uJ and 2.65x; at 200,000 1.4x (lane 7: 1.74x); at 330,000 1.3x (1.33x: agrees, because the 5090's own energy jumps to 4.99 at that rung). (c) Its HBM3 and HBM4 ceilings (10.7 and 21.4 G per stack) rest on the 8-activates-per-12-ns rate chip-model-v3 5.3 carried from O'Connor; the JEDEC HBM2 cycle table (ICCAD 2021 Table I) gives 4 per 28 ns per channel (section 5.1 of this file), and HBM3's own tFAW is behind the paywall. If HBM3 and HBM4 keep HBM2's window the one-stack ceilings are 2.3 and 4.6 G, HBM4's bare energy 0.55 uJ and its bare edge 4.9x, not 11x. The GDDR7 column is the one with a measured anchor (the 5090 reaches 82 percent of its ceiling) and is the column to quote in the summary; the HBM columns are the upper bound.
|
||||
|
||||
Verifier headroom for N (lane 7's "10x of headroom"): on the M5 Max core 10 - 2.33 = 7.67 ms buys 2.4 M shadow instructions, N about 4.5 M ops (19x); on the 2.5x rule 4.2 ms buys 525,000 instructions, N about 1.06 M (10x, lane 7's figure); on the measured half-core proxy 1.77 ms buys 146,000 instructions, N about 370,000 (3.7x). The 10x holds on the rule and not on the pessimistic measured bracket; O-1.14 decides which. Either way the cards bind first: M5 Max 130,000, 5090 at 431 W 210,000, 4070 at 160 W about 250,000, 9070 XT over 331,000. An unconditional doubling of N per era (lane 7's candidate) would take the Apple tier out at the first step (200,000: -10.5 percent) and the 5090 and 4070 at the second (400,000: compute-bound at their caps), which is why the proposal below steps N by signal, not by schedule alone.
|
||||
|
||||
The verdict on v5: the shadow lever is close to spent on the owned cards. Going from 100,000 to 130,000 buys 2.1x to 1.7x against the 5090 at k = 1 and costs the Mac its whole 5 percent allowance; a shuffle-heavy mix buys the k floor 0.3 to 0.5. Together they define one candidate, `v5 = mx8 + sh256x35 with the shuffle-heavy weight table`, worth measuring but not worth a cut on its own: the chip question is k, and no shadow design moves k past about 0.5 against a fixed-datapath array.
|
||||
|
||||
What measurement decides it (not run tonight; the shuffle-heavy weight table does not exist as a knob, and the Mac's miner state was not checked, so the plan stands in for the run): (1) add a shadow weight table to `ShadowClass` (`generator.rs`, 2 hours), draw the block from it, emit it in the three dialects as today; (2) export `mx8+sh256x35` at today's weights and at the shuffle-heavy table for seed igneum-genesis; (3) Mac: `with-lock.sh measure packbench --pack <dir> --batches 60 --batch-log2 24 --group 256` with the IOReport sampler, 2 packs plus the control, about 6 minutes, the miner paused first (`curl -s http://127.0.0.1:60030/.../api/state` from the app's `app.url`, then pause through the app, never from a script); (4) the same ladder as PC jobs on the 5090, 4070 and 9070 XT through the existing `tools/ca3-shadow` playbooks with the card off through the runner's `--cards-off`; (5) `igneum-pow bench` on the Mac core and the box proxy. Pass lines: every owned card within 5 percent of its class v4 rate; bit-exact fingerprints on Metal, CUDA and AMD OpenCL; verifier under 10 ms on the half-core proxy; the 5090's marginal pJ per op on the shuffle-heavy mix read on the three rungs under its cap.
|
||||
|
||||
### 5.4 Task 4: the era draw's randomness
|
||||
|
||||
How it picks. Era n's seed E_n is the 1-hour class-group VDF of `Hash(chain_id || n || blue block hashes of the day before C_era(n))`, where C_era(n) is the highest certified checkpoint at least 7,200 DAA s before the era (spec 04 s4.4). One SplitMix64 stream from E_n draws, in order: the ten op weights perturbed by -2..+2 points each, the output fold rotations, an unused draw for `epoch_len` (set by signal), then under class v3 a second stream draws the load width (pinned at 4 bytes: the draw is consumed), the stride multiplier M (odd) and rotation R, and the four interleave bit positions (spec 01 s1.13.1, `era-layout.md` 1.1). Not drawn: the load count (16), the mixer round count (8) and multiplier (8), the cache and dataset sizes, the item construction.
|
||||
|
||||
What an attacker can bias, priced. Two routes. (a) Forge the certified checkpoint: needs 2/3 of the 30-day blue-block weight, which is 20 days of 100 percent of the network's hash (the headline). Rented at the measured USD 0.0117 per MH/s-hour:
|
||||
|
||||
| Network hash | 20 days of 100 percent, rented | What it buys in the draw |
|
||||
|---|---|---|
|
||||
| 1 GH/s | USD 5,616 | one era's (weights +-2, fold, M, R, pos): a per-card hash-rate spread of 0.8 to 3.2 percent (six eras measured, bench-log "Counter ASIC 2.0, the numbers"), 0 chip effect |
|
||||
| 10 GH/s | USD 56,160 | the same |
|
||||
| 100 GH/s | USD 561,600 | the same; the rental market could not supply 20 pods of any card at 19:00Z on 6 October (bench-log), so a TH/s is not rentable at all |
|
||||
| 1 TH/s | USD 5.6M (not supplied) | the same |
|
||||
|
||||
(b) Re-roll without weight: the miner of the last blue block before C_era(n)'s cut withholds or publishes to change the input set; this costs one block's reward and needs the 3,600-s VDF evaluated inside the 2-s publish window, a 1,800x faster evaluator (spec 04 s4.6: a 300x evaluator beats the epoch's 600 s, not the era's 3,600 s). Even free, one re-roll is one more sample of the same space. So the draw is unbiasable at any price that matters, and that is the honest answer to "what can be biased": nothing worth having.
|
||||
|
||||
Weak corners. The op-weight perturbation can move the multiply share (mul, mad, mulhi: 22 of 75) to 16 or 28 of 75; at the N5 datapath floor that is 0.158 to 0.232 pJ per shadow op (0.195 at the base), about +-20 percent of the shadow's datapath energy, and the GPU's energy moves the same way (its IMAD is the chain's own op). The stride and interleave cost a chip two integer operations per load and an address-line permute, nothing per joule. The fold rotations are a wire mux. The measured six-era spread (1.3 percent on the 5090, 3.2 on the 9070 XT, 0.8 on the M5 Max) is the whole of what the draw moves. There is no corner that makes a chip easier, because no drawn parameter touches the memory bound, the item derivation or N.
|
||||
|
||||
Predictability against the 32-month lead time. Everything a chip needs is public at genesis: the eleven live families, the reserve order R0 to R8 and its era schedule, the mixer shape and x8, the dataset and cache schedule, the class v4 shadow's size and weights. The draw hides (M, R, pos, weights +-2, fold rotations) until 2 hours before each era, and none of those needs silicon: an address decoder that permutes lines, a programmable rotator, an immediate table. So a chip taped out in month 0 against this spec runs every era for the chain's life, and the era draw buys nothing against it. What the draw does buy: the fork-fatigue lesson of the history (no human release, no vote), and a per-program hard-datapath FPGA cannot amortise a bitstream across eras (layer 9 handles the within-era case). Said plainly for the public text: the era draw and the reserve are automatic schedule changes against fixed datapaths and governance, not unpredictability against a chip. What would be unpredictable and costly to a chip is not available in a genesis-fixed rule set: a per-era draw among K reviewed item constructions (K mixers or K derive forms) is still K public blocks a chip carries; a per-era draw of the load count or the dependent-read depth changes the memory bound per era and fails the 5 percent rule on the honest cards (read-width: a per-program width mix spread 5.5 to 22.3 percent). What costs a chip is N (its k), the memory system (f = 1 is the ceiling and is a commodity controller), and the honest card's own watts (the M5 Max at 0.78 uJ is inside 2x of the GDDR7 chip with no shadow at all).
|
||||
|
||||
### 5.5 Task 5: the 2019-class verifier gate
|
||||
|
||||
Measured tonight (section 3.3): on igneum-build-1 one EPYC 9454P core boosted to 3.8 GHz (not a low clock: the governor's cap is 2.75 GHz but the core read 3,800 MHz under load, and `cpupower` needs root) with server DDR5 behind it reads 1.9x to 2.2x the quiet M5 Max core across five classes, and 2.0x on the cache fill. The half-core proxy (both SMT siblings busy on the same class) reads 3.4x to 3.7x the Mac. A 2019 laptop core (Skylake-class at 3.5 to 4.5 GHz with DDR4 at about 80 ns) sits between these brackets on the arithmetic (lower IPC than Zen 4, lower DRAM latency than the server), so the 2.5x rule is about right and the two proxies bracket it. Against the gate:
|
||||
|
||||
| Class | One box core, cold run | Half-core | Verdict at 10 ms | Headroom left for shadow on the half-core (ms) |
|
||||
|---|---|---|---|---|
|
||||
| class v3 (mx8) | 4.67 | 7.56 | pass | 2.4 |
|
||||
| class v4 (mx8+sh256x27) | 5.06 | 8.23 | pass | 1.8 (about 150,000 more shadow instructions at 12.1 us per 1,000) |
|
||||
| dr368 | 5.32 | 8.16 | pass | 1.8 |
|
||||
| dr736 | 10.51 | 15.49 | FAIL on both proxies | none |
|
||||
|
||||
So dr736 is out as a genesis-live or near-term reserve length on measured evidence, not on the 2.5x rule; dr368 is in. The owed O-1.14 measurement on a real 2019 laptop (the US laptop's Windows `igneum-pow` build, main's decision 7) still closes the question; the box proxy is the stand-in until it lands, and the half-core row should be the standing pessimistic rule in place of "2.5x" (it is a measurement; 2.5x is a ratio from memory).
|
||||
|
||||
How to measure it tomorrow: `tools/cross-remote.sh` from `igneum-pow/` builds the Windows exe on the box (1 min 44 s measured for the node; the pow crate is one crate, under a minute), the relay carries it to the US laptop when it appears, `igneum-pow bench --seed igneum-genesis --day 2026-10-03 --class <c> --warps 50` for the five classes, ms per warp into `docs/bench-log.md` under O-1.14; 1 hour of agent work. A rented old CPU on Vast is the fallback (Vast lists CPU-only offers by core generation; a 2019 Xeon or i7 host at under USD 0.10 an hour, approx), same binary, same command.
|
||||
|
||||
What the gate protects, and what loosening it costs:
|
||||
|
||||
| | At 10 ms per warp | At 20 ms (loosened) | At 5 ms (tightened) |
|
||||
|---|---|---|---|
|
||||
| A node on a 2019 laptop at 1 bps | 1 percent of one core per block | 2 percent | 0.5 percent |
|
||||
| At 10 bps (the Devnet 2 experiment) | 10 percent of one core | 20 percent | 5 percent |
|
||||
| IBD over the 108,000-header pruning window, one core | 18 min | 36 min | 9 min |
|
||||
| Header flood (M15 class): invalid headers per second that saturate one core | 100 | 50 | 200 |
|
||||
| A pool core verifying shares | 100 per second | 50 | 200 |
|
||||
| What the lever buys the hash | the x8 mixer, dr368, the v4 shadow all fit with 1.8 ms spare on the half-core | dr736 fits (15.5 on the half-core: no, still out), x16 mixer fits | nothing of class v4 fits on the half-core |
|
||||
|
||||
Loosening to 20 ms would admit the x16 mixer (about 7 ms on the Mac, 15 on the half-core: still out) and not dr736 on the half-core, so it buys little against a chip (the f = 1 chip derives no item) and doubles the header-flood and IBD costs on the weakest node. Keep 10 ms; measure the laptop; use the half-core row until then.
|
||||
|
||||
### 5.6 Task 6: the dataset schedule to 2030
|
||||
|
||||
The schedule (spec 1.13.3 option (b), decided for the cache on 5 October, recommended for the dataset): 2 GiB at genesis, 4 GiB at year 4 (day 1,460), 8 GiB at year 12, 16 GiB at year 28; the cache 256 MiB, 512 MiB, 1 GiB, 2 GiB on the same days (`memhard.rs` `growth_doublings`). The devnet packs run 1 GiB today.
|
||||
|
||||
The installed base against it (Steam September 2026, cited; the trend is approximate):
|
||||
|
||||
| Year (approx calendar) | 8 GB share | 12 GB share | Dataset | Who falls out of mine-only | Who falls out of mine-and-prove (the prover's measured peaks, `prover-tiers-real-cards.md`) |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026 (devnet) | 27% | 13% | 1 GiB | nobody with 4 GB or more | 8 GB: compressed does not fit beside the miner (measured); core-only 2^25 fits with 1 GB spare |
|
||||
| 2027 (genesis, year 0) | about 20% | about 10% | 2 GiB | 4 GB cards hold with 0.5 to 0.8 GB spare (card-lifetime) | 8 GB loses core-only beside the miner (the miner's resident set grows 1 GiB: 7.35 + 1.0 GB over 8.19) and becomes prove-alone; 12 GB holds compressed on headless Linux (10.2 + 1.0 of 12.3) |
|
||||
| 2031 (year 4) | about 0% | about 0% | 4 GiB | 4 GB cards (5 percent of Steam today, about 0 by then) | 12 GB loses compressed (10.2 + 3.0 GB over 12.3), keeps core-only (7.2 + 3.0 of 12.3); 16 GB keeps compressed (9.2 + 3.0 of 16.4) |
|
||||
| 2039 (year 12) | 0 | 0 | 8 GiB | 8 GB cards (26.7 percent of Steam today; the trend says under 1 percent by 2030) | 12 GB loses core-only; 16 GB loses compressed, keeps core-only (7.4 + 7.0 of 16.4); 24 GB keeps compressed (11.0 + 7.0 of 24.6) |
|
||||
| 2055 (year 28) | 0 | 0 | 16 GiB | 12 and 16 GB | 24 GB loses compressed; 32 GB keeps everything |
|
||||
|
||||
Reading per tier: the 8 GB tier, a quarter of Steam today, is never a mine-and-prove card on the measured prover footprint whatever the dataset does, and it mines until year 12, by which time its share on the trend is nil; the 12 GB tier (13 percent, falling 6 points a year) mines to year 28 and loses mine-and-prove compressed at year 4 because the prover peaks at 10.2 GB beside a 1.4 GB miner; the 16 GB tier (27 percent and rising) mines and proves compressed to year 4 and core-only to year 12; 24 and 32 GB are unconstrained to year 28. The dataset is never the binding constraint on any tier before year 12; the prover's 5.6 to 10.7 GB footprint is. On the chip side the schedule changes nothing: one HBM3 stack holds 24 GB and the 5090's own board 32 GB (chip-model-v3 5.7), so f = 1 reads every step of the schedule to year 28 without a second stack.
|
||||
|
||||
What growth is for, then. Two things, both real and neither a chip: (1) the cache above every GPU's on-die cache (96 MB on the 5090, 128 MB on GB202; the spec's own rule), so the honest hash stays DRAM-latency-bound and no consumer GPU gains an L2 shortcut; (2) the dataset above one reticle of SRAM at the node of the day (1.6 GiB per reticle at N5 headline density, 1.9 at N2, sram-mirror.md s4), so an "f = 1 in SRAM" chip, which would read at SRAM latency and beat the DRAM activate ceiling by 10x, stays a multi-reticle part: at 2 GiB that is 2 dies at N2, at 4 GiB 3 dies, at 8 GiB 5 dies (approx, headline density; the lower-bound density halves these). Wafer-scale parts already hold more (a Cerebras WSE-3 carries 44 GB of on-wafer SRAM, approximate, from memory, unpriced here): the schedule does not price that device out and nothing in the hash can, but at the dependent-read pattern a wafer's cross-die hops cost latency that no one has measured for this hash (open, section 7).
|
||||
|
||||
Recommendation with numbers: hold the schedule as decided (2 GiB genesis, doublings at years 4, 12, 28). Do not slow it: slowing buys the 8 GB tier nothing (it mines to year 12 either way) and loses the SRAM-reticle margin (at a flat 2 GiB one N2 reticle holds 1.9 GiB today and about 3.4 GiB by 2036 on the 6 percent a year density trend, sram-mirror s6, so a flat dataset fits one reticle within a decade). Do not grow faster: the only tier a faster schedule costs is the 8 GB tier (year 12 to year 4) and the Apple 8 GB laptop, and it buys nothing against the HBM chip. One change: write the prover footprint, not the dataset, into the public card-lifetime sentence (litepaper line 560: "12 GB or more proves full shards" becomes "12 GB mines and proves on headless Linux until the year-4 dataset step, 16 GB until year 12, 24 GB beyond; 8 GB proves alone"), since that is the number that moves users.
|
||||
|
||||
## 6. Ranked proposals
|
||||
|
||||
| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Close O-1.14 with a real 2019 laptop run and adopt the half-core proxy as the standing stand-in; dr736 out, dr368 in as R0's length | box proxy: dr736 10.5 ms cold, 15.5 half-core; v4 5.06 / 8.23 | section 5.5 | 2 (Windows cross build on the box, relay to the laptop, bench, log) | miners 0; pool verifier cores x1.3 at dr368 instead of x2.4; a 2019 node keeps 1.8 ms of headroom under v4 | ms per warp under 10 cold on the laptop for v3, v4, dr368; dr736 recorded as the figure that fails |
|
||||
| 2 | Measure the FPGA lane on AWS F2 (one VU47P, HBM2, USD 1.98 an hour) and replace the 12.2 ceiling row | the measured 2.4 G/s equals the JEDEC tFAW ceiling; the 1.9x row rests on a 12 ns tFAW the JEDEC cycles do not support | section 5.1 | 8 to 10 agent hours plus USD 2 to 8 of F2 time | none today; the public FPGA claim becomes a measured 0.3x to 0.5x per watt | reads per second per watt at 1 GiB; alarm at 27 M/s/W, Counter ASIC 4.0 at 54 |
|
||||
| 3 | Public-text correction: the era draw and the reserve are automatic schedule changes against fixed datapaths and against forks, not unpredictability against a chip; publish the chip's USD per MH/s-hour beside the honest cards' | section 5.4: every drawn parameter needs no silicon; the reserve is about USD 4 of N5 silicon on a chip | sections 5.1, 5.2, 5.4 | 1 | holders and miners read a claim that survives review; nothing on the devnet changes | `docs/evidence.md` row with the two numbers (USD 0.00021 chip, 0.00113 owned 5090, 0.0117 rented) and the draw sentence |
|
||||
| 4 | Reserve order: R1 shfla, R2 perm, R3 popc and clz, R4 to R7 unchanged, R8 mm8 at era 8 by the rule; R0 at dr368 | AMD step cost of shfla measured 0.75 to 0.84 (the one number the reserve document said could move R3); dr736 fails the proxy | section 5.2 | 1 (spec text in `counter-asic-3-reserve.md` s5 and s6) | Apple pays shfla's 1.91x per op first, under 1 percent of ALU time at 4 points (argued, measured at the unlock rehearsal); NVIDIA and AMD 0 | the family-live 5 percent run per vendor at each unlock rehearsal |
|
||||
| 5 | **N grows by the era draw at genesis, inside a verifier-bounded ladder, each step taken by 90 percent miner signal.** The shadow size N becomes a genesis ladder indexed per era, {100,000, 130,000, 200,000, 330,000, 650,000, 1,000,000} counted ops (the measured rungs, then doublings), floor 100,000 and ceiling 1,000,000 fixed at genesis (the ceiling is the 10x verifier headroom on the 2.5x rule; 370,000 on the half-core proxy until O-1.14 lands, which then sets it), the era stream consuming one draw for it as it does for `epoch_len`, and the step up or down set by 90 percent of blue blocks over 7 days at a day boundary (spec 5.7's mechanism, P2's signalling code), never unconditionally | lane 7: HBM4 doubles the f = 1 chip's rate per stack, so the bare edge rises 5.7x to 12x (upper bound) and only N answers it; this lane: the chip's edge over the 5090 at k = 1 falls 2.1x (100,000) to 1.7x (130,000) to 1.3x (200,000 and 330,000); an unconditional doubling takes the M5 Max out at the first step | section 5.3a; `--section ladder` | 6 to 8 (the ladder field in `ShadowClass` and the era stream, the signal rule shared with `epoch_len`, a fast-time run across one step, packs and vectors per rung) | At each step, measured: 100,000 to 130,000 costs the M5 Max 3.3 points of rate and 0 W more, the 5090 and 4070 nothing, the 9070 XT nothing, every verifier +0.05 ms; 130,000 to 200,000 costs the M5 Max 6 more points and the 5090 2.7 at its cap, the 4070 +21 W, every verifier +0.12 ms (Mac) to +0.5 (half-core); 200,000 to 330,000 is compute-bound on every NVIDIA card at its cap (5090 -35 percent) and is a step the signal would refuse until cards change; a pool user nothing at any step; a chip's shadow core grows with N at k x 11 pJ per op | ONE gate per step, published before the project recommends the signal: at the step's N, every card of the public benchmark set (the four owned plus the eleven rented models) within 5 percent of its rate at the previous step, bit-exact fingerprints on Metal, CUDA and AMD OpenCL, and the verifier under 10 ms per warp cold on the O-1.14 core (the half-core proxy until then) |
|
||||
| 5a | A class v5 candidate `mx8 + sh256x35` with a shuffle-heavy shadow weight table (shfl 14, shfla 8 of 75), measured on the four owned cards before any cut: the first rung of proposal 5's ladder, plus the k-floor lever | the Mac's 5 percent point is 130,000; the shuffle mix raises the k floor 0.32 to 0.46 (approx) | section 5.3 | 4 to build the weight-table knob and packs, 1 Mac measure session (about 6 min under the lock, miner paused), 3 PC jobs | M5 Max -4.8 percent of rate at 37 W; 5090 -0.3 percent at its 431 W cap (a rig +23 percent electricity); 4070 0 at about 118 W; 9070 XT 0; verifier +0.23 ms Mac, +0.85 half-core | every owned card within 5 percent; bit-exact on three vendors; half-core verifier under 10 ms; the 5090's marginal pJ on the new mix read on three rungs |
|
||||
| 6 | Hold the dataset schedule (2 GiB, years 4, 12, 28); write the prover footprint into the card-lifetime sentence | Steam shares and the measured prover peaks; one HBM3 stack holds every step | section 5.6 | 1 | 8 GB: mines to year 12, proves alone; 12 GB: mine-and-prove compressed to year 4, core-only to year 12, mines to year 28; 16 GB: compressed to year 4, core-only to year 12; 24 and 32 GB unconstrained to year 28 | the litepaper sentence matches the table; `docs/evidence.md` row "card lifetime" labelled designed |
|
||||
| 7 | Make the Ember tune the shipped default per card model (the honest card's watts are the lever that moves every chip row) | the 4070 at 3.65 uJ untuned and 2.57 tuned (-30 percent); the 5090 2.65 bench against 2.34 app | section 5.1 | 2 (defaults table in the app from the fleet priors; already measured) | every NVIDIA tier gains 10 to 30 percent per joule; the chip's edge over the mid-tier falls from 8x to 13x toward 5x to 9x at v3 | MH per W per card model on the fleet night against the untuned baseline |
|
||||
| 8 | Fund the k question: the item 3 cryptanalysis brief gains a chip-design line (a 14,000-lane SIMD array's energy per op on a random 32-lane program with shuffles, at N5 and at 28 nm) | every chip row at class v4 turns on k; nothing in the project measures it | section 5.3 | 0 agent hours; the project lead's money (part of the USD 80,000 to 160,000 brief) | none until the number lands; it decides whether 2x is reachable | a reviewed estimate of k with its range |
|
||||
|
||||
Paragraphs.
|
||||
|
||||
1. The verifier gate is the one place tonight produced a measurement instead of a rule. The box proxy brackets a 2019 laptop from both sides (a 2022 server core at full boost; the same core with its sibling busy), and dr736 fails both brackets while class v4 passes both with 1.8 ms to spare. The measurement is one Windows build and one bench on the laptop the project lead already owns; until it lands, the half-core row replaces the "2.5x" from memory in every status file.
|
||||
|
||||
2. The FPGA lane's upper row was built on an activate rate (8 per 12 ns per channel) that the JEDEC HBM2 cycle table does not support (4 per 28 ns); the measured Shuhai rate sits exactly on the JEDEC ceiling. That reading can be wrong (the ICCAD table's clock interpretation, the half-bank count, the board watts are all approximate), which is why the F2 hour is the proposal and not the conclusion. It is cheap and it turns a public ceiling claim into a measured one.
|
||||
|
||||
3. The honest statement about the draw and the reserve is owed before the public testnet. The project has said the era draw and the family reserve are "automatic anti-ASIC escalators"; against the chip that the model says anyone would build they escalate nothing, because every parameter they move is firmware or a USD 4 block. They are good design against forks and against a hard-datapath FPGA, and that is what the text should say. The chip's USD per MH/s-hour (56x under rental, 5.4x under an owned 5090) belongs beside it, because it is the number a miner will compute on the day a chip appears.
|
||||
|
||||
4. The reserve order changes only where the measurements moved: shfla's AMD cost came in cheap, so the largest structure goes first; dr736 failed the proxy, so R0 is dr368. mm8 keeps no exception because `epoch-length.md` 12.4 showed it removes no adversary class.
|
||||
|
||||
5. N as a genesis ladder is the one structural change this lane proposes, and it is lane 7's idea with the cards' measured bind points written into it. The memory generation it answers (HBM4, 2028) arrives on a two-to-three-year cadence; the chain must answer without a release, which the era stream and the `epoch_len` signal rule already provide the shape for. What the measured rungs add: the ladder's steps are the cards' own bind points, the step is taken by the miners who pay for it, and the ceiling is the verifier's measured core, not a 10x from a ratio. An unconditional doubling per era would retire the Apple tier at era 1 and every capped NVIDIA card at era 2, so the schedule alone is not the proposal; the schedule plus the signal is.
|
||||
|
||||
5a. v5 as a class (`mx8 + sh256x35` with a shuffle-heavy mix) is measurable in an evening and is not a cut. Its honest ceiling is the Mac's 5 percent and a k floor of about 0.5; it buys 2.1x to 1.7x against the 5090 at k = 1. The design item that decides more than v5 is k itself (proposal 8).
|
||||
|
||||
6. The dataset schedule is right as decided and the public sentence about cards is wrong in kind: it talks about the dataset when the prover is what ends a tier's mine-and-prove life.
|
||||
|
||||
7. The one lever that moves every chip row and costs no consensus change is the honest card's watts. The fleet showed the untuned mid-tier at 1.5 to 2.3x the 5090's energy per hash; Ember's measured tune on the 4070 took 30 percent off. Shipping it as the default is a miner-app change with a measured gate.
|
||||
|
||||
8. k is the whole chip question at class v4 and nobody in the project can measure it; the external brief can estimate it.
|
||||
|
||||
## 7. Open questions and what could not be run
|
||||
|
||||
| Item | Why not | What would close it |
|
||||
|---|---|---|
|
||||
| The v5 Mac packbench ladder | the shuffle-heavy shadow weight table does not exist as a knob (the block uses the program's weights), and the Mac's miner state was not checked; a run without the knob would have measured v4 again | proposal 5, 4 hours of code then the 6-minute measure session |
|
||||
| A fixed low clock on the box | `cpupower` needs root; the core boosted to 3.8 GHz under schedutil | the laptop measurement (proposal 1); the half-core row is the pessimistic stand-in |
|
||||
| The 9070 XT watts at class v4 and its per-joule row | the AMD watts job failed on 6 October and the re-run waits on the runner's `--cards-off` (status 6a) | the next cut's PC 1 job |
|
||||
| The RTX 4060's watts (logged 0.0 W on the rented box) | the sampler read nothing on that host | one re-rent |
|
||||
| HBM2 tFAW and half-bank count on the U55C and F2 parts | behind the JEDEC paywall; the ICCAD table is a simulator's configuration (DRAMSim3), not a datasheet, and its clock interpretation (1,066 MHz) is mine | the F2 hour (proposal 2) |
|
||||
| A wafer-scale SRAM dataset holder (Cerebras-class, 44 GB on-wafer, approximate) | not priced anywhere in the project; its cross-die hop latency on a dependent-read chain is unmeasured | a Counter ASIC 4.0 analysis item, not a lane 2 item |
|
||||
| The Steam trend to 2030 | linear extrapolation of two points per tier | the survey itself, yearly |
|
||||
| The era draw's cryptanalysis (the stride bijection, the ROT weak-key draw) | out of scope here and still open in spec 1.8.4 and era-layout s8 | the item 3 brief |
|
||||
| `block-rate-devnet2.md` RUN_A and RUN_B | placeholders at 21:30 UK | nothing in this lane depends on them |
|
||||
|
||||
## 8. Summary for the coordinator
|
||||
|
||||
Lane 2 refined the shipped hash and its classes on the 6 October numbers and one new measurement. The chip model's answer does not change in kind: the stored-dataset chip is the chip, it reads 5.7x per joule against the 5090 bench row and 1.7x against the M5 Max at class v3, 2.1x and 0.9x at class v4 and k = 1, and it undercuts rented hash 56x and an owned 5090 5.4x per MH/s-hour; the FPGA lane tightens to 0.30x to 0.47x per watt because the measured random-read rate of an HBM2 FPGA is the JEDEC activate ceiling and not a mapping artefact, and AWS F2 can measure it for USD 2 an hour. The reserve and the era draw buy nothing against a chip (every drawn parameter is firmware; every reserve block is about USD 4 of silicon) and the public text should say what they do buy. The verifier gate got its first measured proxies: class v4 passes a 2022 server core (5.06 ms cold) and the same core with its SMT sibling busy (8.23 ms); dr736 fails both (10.5 and 15.5 ms), so R0 is dr368. The dataset schedule holds; the tier constraint to 2030 is the prover's footprint, not the dataset.
|
||||
|
||||
1. Class v4 verifier on the box proxy 4.90 ms steady, 5.06 cold, 8.23 on the half-core; dr736 9.76 / 10.51 / 15.49: dr736 is out on measurement, class v4 keeps 1.8 ms under the gate on the pessimistic bracket (section 5.5; `sim/horizon/algorithm/model.py --section verifier`).
|
||||
2. The f = 1 GDDR7 chip's edge per joule: 5.7x (5090 bench), 1.7x (M5 Max) at v3; 2.1x and 0.9x at v4 with k = 1; 4.1x and 1.7x at k = 0.3; against the untuned rented mid-tier 8x to 13x at v3; USD 0.00021 per MH/s-hour against 0.0117 rented (section 5.1; `--section chip`).
|
||||
3. The HBM2 FPGA soft overlay: 2.3 to 2.9 G reads/s per 2-stack card by tFAW and tRRD (measured 2.4), 0.30x to 0.47x of the 5090 per watt; the 1.9x ceiling needs a tFAW of 12 ns that the JEDEC HBM2 table (28 ns) does not give (section 5.1; `--section fpga`).
|
||||
328
docs/analysis/horizon/consensus-security.md
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
# Horizon lane 1: consensus security. What a hash majority buys on Igneum, attack by attack, with the bound and the price
|
||||
|
||||
Date: 6 October 2026, evening UK (written 19:40 to 21:30 UTC). Lane: consensus-security. Worktree: `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon` at `3f4f719`). Companion paper: `docs/analysis/51-percent.md`. Models and runs: `sim/horizon/consensus-security/` (README at the end of this file, section 9).
|
||||
|
||||
What was read before modelling: `docs/spec/02-consensus.md`, `03-finality.md`, `04-seeds-and-vdf.md`, `06-open-items.md`, `07-execution.md`, `08-client-security.md`; `docs/fud-ledger.md` F1 to F25, M14, M15, M23, M24, P7, P9, P11, P12, X18 to X20, G8, G12, G13, C4, D6, E16; `docs/analysis/difficulty-2026-10-03.md` (section 11), `difficulty-2026-10-04-oscillation.md`, `sim/difficulty/attacks/README.md` (the seven attacked ways); `sim/results_v2.md` A to M; `docs/benchmarks/finality-v3-2026-10-04/*.md`, `docs/benchmarks/round4-consensus-2026-10-04/results-final2.md`; `tools/finality-attacks/README.md` and `lib/net.mjs`, `tools/harness/README.md` and scenarios, `tools/exec-attacks/README.md`; `docs/review/redteam-2026-10-04.md`, `docs/review/round-4-reddit-2026-10-06.md`; `docs/plans/counter-asic-3-node.md` section 6 (P2); `docs/analysis/security-budget.md`; `docs/bench-log.md` "Rental cost of hash, 6 October 2026"; `vendor/igneum-node/consensus/src/processes/ghostdag/protocol.rs` (main checkout); `infra/fast-time/README.md` and `override-60x.json`; CLAUDE.md's 6 October rules. Two facts from main during the lane (19:4xZ, the first later corrected): the live devnet's finality has been paused since 18:39:40Z (lane 3 confirmed the cause at c3aa502: a 42.7 percent departure held by the frozen table, not the hub outage); and run A on igneum-devnet-2 at 10 blocks/s in a star of 41 miners through one seed gave 12.4 blocks/s, 77 percent red blocks, 321 tips, max reorg 55, with the controller lowering difficulty on the low blue rate (cite as "main, 6 Oct 2026 19:4xZ, block-rate-devnet2.md run A" until the file carries the rows).
|
||||
|
||||
Price basis for every cost: USD 11.7 per GH/s-hour, measured on RunPod community pods on 6 October 2026 (`docs/bench-log.md`, "Rental cost of hash": 1,748 MH/s for USD 20.44 an hour; approximate above 2 GH/s because the market supplied no more; the live devnet was 1.16 GH/s). Costs are given per network size at 1, 10, 100 GH/s and 1 TH/s. Writing rules: no em dashes, numbers in tables, every figure labelled measured, simulated, cited or approximate.
|
||||
|
||||
## 1. Summary for the coordinator
|
||||
|
||||
A hash majority on Igneum buys the ordering race inside the lock latency and nothing a certificate covers, and that is measured here, not asserted. Three findings lead.
|
||||
|
||||
1. **The lock does the work the k-cluster cannot.** The DAG simulator (`ghostdag_sim.py`, GHOSTDAG as `protocol.rs` runs it) shows a 45 to 51 percent withholder wins the selected-chain race over a 90-second hold 70 to 85 percent of the time, reorganising 32 to 46 chain blocks at 1 block/s; a 34 percent withholder wins only inside 30 to 60 s (40 percent of attempts) and never at 90 s; a 20 percent one only the last k = 18 blocks (5 to 15 percent of attempts). Finality's lock lands about 63 to 93 s after a checkpoint block (spec 03 C1, 3.11.3), so the window a majority can reorder is the lock latency, measured at 90 to 120 s, not a block count. Below two thirds of weight, no hash share reaches past a certificate (spec 03 3.11.2, `sim/results_v2.md` H, I, L3, M5: 0 conflicts under 1/3 in every seed).
|
||||
2. **The pause is the residual, and it is cheap to buy and free to hold.** Reaching the veto (1/3 of 30-day weight) at 51 percent of blocks takes 20 days and costs USD 6k at 1 GH/s, USD 5.8M at 1 TH/s in rent (`cost_model.py`), of which the attacker earns 51 percent back as subsidy; once held, silence costs nothing (the silent key keeps mining and earning) and pauses finality for as long as it likes (`finality_horizon.py` S: at 34 to 90 percent silent, 0 locks for the whole silence, 0 conflicts). During a pause the chain is proof of work with a 12-hour depth, and a 12-hour 51 percent double spend costs USD 146 at 1 GH/s and USD 146k at 1 TH/s. Tonight's pause is the departure case, confirmed by lane 3 (`docs/analysis/horizon/finality-and-weight.md` 3.1): 20 keys holding 42.7 percent of the frozen table stopped mining in three minutes, the signing weight fell to 53.1 percent at checkpoint 6843, the frozen table (Q5) holds the pause for a window (2 h on the devnet, 30 days on mainnet) where rule v2 would have locked after 35 minutes; certificates formed while the hub was down, so the topology hypothesis is refuted. The signed departure (LEAVE, lane 3's rank 1) is the fix.
|
||||
3. **The proving pool is capturable by any block producer today, in proportion to its hash and up to most of it.** Consensus checks a proof record's statement against native execution and its signature, and NOT the proof (spec 07 7.7 item 4, 7.8 item 8); the first valid record carried pays. A producer that writes a correct statement with random proof bytes into its own block is paid the shard; at 51 percent of blocks it takes at least 51 percent of the 20 percent pool (11,636 IGN an hour at full subsidy) and, because its fake lands in its next block while honest records need 9 to 11 s of proving, most of the shards outside the 10-s exclusive window. This is ledger P21 priced: the only line in the table where a hash majority earns more than it spends. The fix is proof verification in consensus (section 6, rank 1).
|
||||
|
||||
The ranked proposals are in section 6. Two cost nothing in liveness and close whole classes: proof verification in consensus (rank 1) and weight-gated deep fork choice (rank 2: a chain forked deeper than D seconds is a candidate only if the keys that built it hold a third of the weight table at the fork, so rented hash cannot reorg past D even during a pause). Two cost liveness and are not recommended as asked: prover attestations as a second finality leg, and any rule that re-enables locks under the frozen table after an abrupt departure, because a view cannot tell a departure from a partition.
|
||||
|
||||
## 2. Method
|
||||
|
||||
| Model | File | What it does | Machine, lock, seeds |
|
||||
|---|---|---|---|
|
||||
| GHOSTDAG withholding | `sim/horizon/consensus-security/ghostdag_sim.py` | Abstract DAG: Poisson arrivals at 1 and 10 blocks/s, 8 equal honest miners publishing at once, one uniform one-way delay d, one attacker at share H withholding (hold T then release; or selfish: release when about to lose or at lead 6), GHOSTDAG coloring and selected parent exactly as `protocol.rs` (k-cluster with `blues_anticone_sizes`, topological mergeset, blue work), 10 parents. Reports, from honest miner 0: reorg depth in chain blocks against the chain it held at release, fork age, whether the private tip became the chain, attacker blue share, honest blocks turned red | Mac, `with-lock.sh run nice -n 19`, 20 seeds per cell; 1 bps k 18 (`ghostdag_results_1bps.md`, 11 s), 10 bps k 124 (`ghostdag_results_10bps.md`, 223 s); the star check inline (section 4.3) |
|
||||
| Finality sweep | `sim/horizon/consensus-security/finality_horizon.py` | Imports `sim/finality_v2.py` unchanged (1,000 Pareto keys, 3 regions, 2-s delay, 2.2 percent outage, no DAG, perfect retarget, keys free); adds six sweeps over the adversary's share 20, 34, 51, 67, 90 percent: renter, silent set, bought keys, poisoned eclipse, partition with an equivocator under v2 and v3, abrupt departure under v2 and v3 | Mac, run lock, seeds 7 and 11; `finality_horizon_results.md` |
|
||||
| Signalling game | `sim/horizon/consensus-security/signalling.py` | Arithmetic on P2 (95 percent of a one-day blue-block window, floor height): binomial noise, holdout cost, forced-flip cost, signal-then-defect | Mac, instant; `signalling_results.md` |
|
||||
| Cost table | `sim/horizon/consensus-security/cost_model.py` | Every attack's rented hash A/(1 - A) x N, its duration from the spec's arithmetic, its cost at 1 to 1,000 GH/s, what it earns at the three IGN price inputs of `security-budget.md`, and the equilibrium network where rent equals subsidy | Mac, instant; `cost_results.md` |
|
||||
| Node harness | `tools/finality-attacks/run.mjs s6 --fast-time` against the 0.3.14 Mac binary (`vendor/igneum-node/target-0314/release`, built 6 Oct 17:56), rule v3 forced on, SCALE 0.4, ports 29800+, suffix 980, `/tmp/igneum-horizon-fin` | The 3/3 and 4/2 partitions on a real DAG | Mac, run lock; result in section 4.5 (or the recorded runs if the node refused the file) |
|
||||
|
||||
Where a number is from a recorded run and not re-run tonight it is cited by file. Nothing live was touched; no tracked file outside this lane's three paths was edited.
|
||||
|
||||
## 3. Evidence: the measured and simulated numbers
|
||||
|
||||
### 3.1 Ordering: what a withholder does to the selected chain (simulated, `ghostdag_results_1bps.md` section 2, 20 seeds, d = 0.67 s = the cloud devnet's p99 propagation, ledger F7)
|
||||
|
||||
| attacker share H | hold 30 s | hold 60 s | hold 90 s | hold 120 s |
|
||||
|---|---|---|---|---|
|
||||
| 20% | won 10%, reorg med 0 / max 18, att blue 65% | 0%, 0 / 1, 38% | 0%, 0 / 1, 21% | 0%, 0 / 1, 18% |
|
||||
| 34% | won 40%, 0 / 18, 77% | 40%, 0 / 36, 55% | 5%, 0 / 39, 35% | 10%, 0 / 52, 28% |
|
||||
| 45% | won 70%, 10 / 17, 94% | 75%, 22 / 32, 84% | 70%, 33 / 46, 80% | 70%, 46 / 57, 80% |
|
||||
| 51% | won 85%, 10 / 15, 90% | 85%, 20 / 28, 88% | 80%, 32 / 44, 86% | 80%, 42 / 53, 84% |
|
||||
| 67% | won 100%, 8 / 13, 98% | 100%, 16 / 23, 98% | 100%, 26 / 31, 99% | 100%, 34 / 41, 99% |
|
||||
| 90% | won 100%, 2 / 4, 98% | 100%, 4 / 9, 99% | 100%, 6 / 13, 99% | 100%, 9 / 17, 99% |
|
||||
|
||||
"won" = the private tip became honest miner 0's selected chain; "reorg" = chain blocks removed from the chain it held at release (median / max over seeds); "att blue" = the attacker's blue blocks over its blocks in the honest view (its weight and subsidy kept). The fork age when it wins equals the hold (30 to 122 s). At d = 5 s (Kaspa's design bound) the same shares win more often at short holds (34% wins 100% at 30 s) and the same at long ones. Natural reorg depth with no attacker: p99 2, max 2 to 3 at d 0.35 to 0.67 s (the cloud devnet measured p99 3, max 5 over 37,113 removals, ledger F7); p99 15 at d = 5 s.
|
||||
|
||||
The arithmetic behind the table: the attacker's private chain merges honest blocks as blue until its first private block has k honest blues in its anticone, then every later honest block is red in that chain. So the private tip's blue work is R + min(k, N_h) against the honest tip's N_h, with R = H lambda T and N_h = (1 - H) lambda T: it wins whenever R + k > N_h, that is for every T when H >= 1/2 and for T < k H / ((1 - 2H) lambda) below it (6 s at 20%, 56 s at 34%, 180 s at 45%, with Poisson noise around each). The honest blocks it turns red (hon red: 25 to 46 percent of honest blocks at 45 to 51 percent and 60 to 120 s holds; 4 to 14 percent at 34 percent) pay their 80 percent to the attacker when its chain wins (spec 02 2.5, the red rule), so a sustained withholder at or above 45 percent takes honest subsidy and raises its weight share; at 20 to 34 percent its own blocks go red and it loses both.
|
||||
|
||||
Weight share a repeating withholder settles at, derived from the 60-s rows (the longest hold that beats the lock on every checkpoint, section 5.1), approximate: w = H x attblue / (H x attblue + (1 - H)(1 - honred)).
|
||||
|
||||
| H | 20% | 34% | 45% | 51% | 67% | 90% |
|
||||
|---|---|---|---|---|---|---|
|
||||
| weight share under sustained 60-s withholding | 9% | 25% | 48% | 56% | 77% | 95% |
|
||||
| blue rate the difficulty controller reads (share of true) | 86% | 76% | 79% | 80% | 86% | 94% |
|
||||
|
||||
So 51 percent of hash reaches about 56 percent of weight by red-flooding the honest side, still 11 points under two thirds; the honest miners lose about a quarter of their subsidy for as long as it lasts, and the chain reorganises every minute, in public.
|
||||
|
||||
### 3.2 Ordering at 10 blocks/s (simulated, `ghostdag_results_10bps.md`, k = 124)
|
||||
|
||||
| measure | d = 0.35 s | d = 0.67 s | d = 2 s |
|
||||
|---|---|---|---|
|
||||
| natural reorg depth p99 / max, no attacker | 11 / 15 | 22 / 26 | 99 / 109 |
|
||||
| 51% hold 30 s: won, reorg med / max | - | 100%, 50 / 58 | - |
|
||||
| 51% hold 90 s | - | 100%, 146 / 163 | - |
|
||||
| 34% hold 30 s / 60 s | - | 95% (59 / 64) / 0% | - |
|
||||
| 20% hold 10 s / 30 s | - | 45% (10 / 33) / 0% | - |
|
||||
|
||||
At 10 blocks/s the same shares reorganise ten times the chain blocks in the same seconds, and the natural reorg depth at a 2-s delay (99) is above the mainnet checkpoint depth d = 60. C1's rule that d scales with the rate (d = 60 B, ledger F7 round 2) is load-bearing; at 10 blocks/s with d = 600 the determination sits 60 s behind the checkpoint as at 1 block/s.
|
||||
|
||||
### 3.3 The star: run A reproduced (simulated inline, section 4.3 code; 10 bps, k 124, 8 miners, honest only, 120 s)
|
||||
|
||||
| uniform relay delay d, s | blocks in flight | honest blocks red | natural reorg p99 / max at miner 0 | reading |
|
||||
|---|---|---|---|---|
|
||||
| 0.67 | 7 | 0% | 19 / 23 | healthy |
|
||||
| 2 | 20 | 0% | 78 / 99 | reorgs grow with d |
|
||||
| 5 | 50 | 0% | 8 / 133 | still under k |
|
||||
| 10 | 100 | 31% | 0 / 0 | past k/lambda: miners stop switching, each on its own chain |
|
||||
| 15 | 150 | 50% | 0 / 0 | |
|
||||
| 20 | 200 | 60% | 0 / 0 | |
|
||||
| 30 | 300 | 67% | 0 / 0 | run A's 77% red sits beyond this row |
|
||||
|
||||
Once the effective delay passes k / lambda (12.4 s at k 124 and 10 blocks/s), the DAG stops converging: every miner's own tip is heaviest in its own view, red climbs to two thirds, and the "reorg 0" rows are the absence of consensus, not its presence (run A's 321 tips). Through one hub relaying 12 blocks/s to 41 peers the effective delay is the hub's validation and relay time, which the fleet measures and this lane does not; the model says 77 percent red needs 30 s or more of it, approximate. The controller then reads the blue rate as a third of the true rate and eases, which widens the DAG further (section 4.2 attack 8). Lane 5 owns the fix; the attack bound is in 4.2.
|
||||
|
||||
### 3.4 Finality weight over the adversary's share (simulated, `finality_horizon_results.md`, seeds 7 and 11; the table is inserted in section 3.5 from the run)
|
||||
|
||||
### 3.5 Finality sweep results
|
||||
|
||||
Condensed from `finality_horizon_results.md` (seeds 7 and 11; the full tables are there). A = the adversary's share.
|
||||
|
||||
| sweep | 20% | 34% | 51% | 67% | 90% |
|
||||
|---|---|---|---|---|---|
|
||||
| R renter, signing: day it reaches 1/3 / 2/3 (sim; formula 10/A, 20/A) | never / never | 30.0 / never | 20.0 / never | 15.0 / 30.0 | 12.0 / 23.0 (formula 11.1 / 22.2; the dust effect of B) |
|
||||
| S silent set keeps mining, 6 h: locks while silent, first lock after resume | 100%, 0 min | 0%, 0 min (720 stalled) | 0%, 0 min | 0%, 0 min | 0%, 0 min; 0 conflicts in every row |
|
||||
| K bought keys worth A, buyer mines 30%: veto held (days) / stalls if silent (of 86,400) | never / 816 to 1,045 | day 1 to 6 / 36k to 42k | day 1 to 24 / 74k to 76k | day 1 to 26 / 79k to 80k | day 1 to 27 / 82k; share at day 30 is 30% in every row; 0 conflicts |
|
||||
| E poisoned eclipse, 20% pool, 2 h: conflicting locks (eclipsed side holds 20% + A) | 0 (40%) | 0 (54%) | 67 to 70 from minute 2 (71%) | 30 to 32 from minute 4 (87%) | not run: the attacker alone is over 2/3 |
|
||||
| P 50/50 partition with an equivocator, 150 min, v2 and v3: conflicting locks, first at | 0 | 21 to 70, minute 14 to 78 (the knife edge) | 299 to 300, minute 0 | 301, minute 0 | 293 to 300, minute 0; every pre-heal lock kept, 0 post-heal stalls, v3 = v2 in every cell |
|
||||
| C abrupt departure (stops mining and signing): first lock, days, v2 / v3 (analytic v2 30(1 - 1/(3A))) | 0.00 / 0.00 | 0.8 / 30.0 (0.6) | 10.5 / 30.0 (10.4) | 15.2 / 30.0 (15.1) | 18 to 19 / 30.0 (18.9); 0 conflicts |
|
||||
|
||||
Readings. R: the formula holds to 0.1 day; 51 percent never reaches two thirds. S: from one third upward the pause is exactly the silence, free to the silent set. K: bought weight is worth its blocks and decays; a silent 51 percent buyer pauses finality for 24 days then loses the veto. E: an eclipse cannot produce a conflict below the one-third equivocator bound whatever the pool; above it the conflict is the equivocator's, not the eclipse's. P: the bound is one third in every view under both rules, as 3.11.2 says; at 34 percent the model's 2.2 percent outage makes it intermittent (21 to 70 of 300 indices). C: under v2 the pause after a departure ends when the survivors fill two thirds of the sliding table; under v3 (the live rule since 135,200) on day 30 whatever the share; on the devnet's 2-hour window those days are minutes: 2 h after the last lock under v3, which for tonight's 18:39:40Z lock is about 20:40Z (approximate, if the departed boxes hold over a third of the frozen table and do not return).
|
||||
|
||||
### 3.6 Signalling (arithmetic, `signalling_results.md`)
|
||||
|
||||
| fact | value |
|
||||
|---|---|
|
||||
| noise on a one-day window share at p = 0.95 | 0.07 points; a 94.5% fleet never flips, a 95.1% fleet flips on day one (P 0.91) |
|
||||
| 6% holdout rent per day at 1 / 10 / 100 / 1,000 GH/s | USD 18 / 179 / 1,792 / 17,923 (it earns 6% of the subsidy meanwhile) |
|
||||
| forced flip, 95% of one day's blue blocks (19 N for 24 h) | USD 5,335 at 1 GH/s, 53k at 10, 534k at 100, 5.3M at 1 TH/s; the market could not supply a TH/s on 6 Oct |
|
||||
| signal then defect | the defector's blocks fail PoW under the new program and are refused; cost falls on it alone |
|
||||
|
||||
### 3.7 Cost of every attack (arithmetic, `cost_results.md`, excerpt; the full table has 12 rows x 4 network sizes)
|
||||
|
||||
| attack | share, duration | rent at 1 GH/s | at 100 GH/s | at 1 TH/s | subsidy earned meanwhile (IGN) |
|
||||
|---|---|---|---|---|---|
|
||||
| win the lock-latency race (90 s) | 51%, 90 s | USD 0.3 | USD 30 | USD 304 | 1k |
|
||||
| 12-h double spend during a pause or the first 30 days | 51%, 12 h | USD 146 | USD 15k | USD 146k | 559k |
|
||||
| orphan an hour beyond merge depth during a pause | 51%, 1.5 h | USD 18 | USD 2k | USD 18k | 70k |
|
||||
| the veto, 1/3 of weight | 51%, 20 d | USD 6k | USD 585k | USD 5.8M | 22M |
|
||||
| lock alone, 2/3 of weight | 67%, 30 d | USD 17k | USD 1.7M | USD 17.1M | 44M |
|
||||
| long-range private DAG over the window (cold start) | 51%, 30 d | USD 9k | USD 877k | USD 8.8M | 34M |
|
||||
| hold a pause once the veto is held | 0 marginal | 0 | 0 | 0 | keeps earning |
|
||||
| take the hands or the 8 aggregators down | 0 hash | DoS cost only | | | |
|
||||
| fake proof records as a block producer | 0 extra hash | 0 | 0 | 0 | up to the whole 20% pool |
|
||||
|
||||
At the rental-market equilibrium (hash joins until rent equals subsidy: 39, 156 and 780 GH/s at USD 0.005, 0.02 and 0.10) the veto nets about 48 percent of 20 days of the chain's subsidy and locking alone about 33 percent of 30 days; the 12-hour pause-time double spend costs about 12.5 hours of subsidy.
|
||||
|
||||
## 4. The attacks, by layer: what each achieves, the defence, the bound, the price
|
||||
|
||||
The hash shares H run 20, 34, 51, 67, 90 percent in every table; "rent" is A/(1 - A) x N x hours x USD 11.7 and the four network sizes are 1, 10, 100, 1,000 GH/s.
|
||||
|
||||
### 4.1 GHOSTDAG ordering
|
||||
|
||||
| attack | what it achieves (sim) | defence | bound | rent (1 GH/s to 1 TH/s) | earns |
|
||||
|---|---|---|---|---|---|
|
||||
| Selfish mining on the DAG (release when about to lose, lead 6) | blue share equals hash share within 0.3 points at every H (`ghostdag_results_1bps.md` section 3): honest blocks are merged, not orphaned, so there is no relative gain; max reorg 1 to 3 chain blocks | GHOSTDAG merges parallel blocks; a red block pays the merger | 0 gain; the attacker's reds are its loss | 0 extra | nothing |
|
||||
| Withholding to reorder (double spend) | 20%: the last k blocks, 5 to 15% of attempts; 34%: 30 to 60 s, 40%; 45 to 51%: the whole hold, 70 to 85%, 32 to 46 chain blocks at 90 s; 67%+: every attempt | the lock: a candidate tip must pass through every certified checkpoint (spec 03 F1); the checkpoint block locks 63 to 93 s after it is mined | reorg depth = the lock latency, 90 to 120 s (spec 3.11.3), whatever H under 2/3 of weight; credit on the lock only (P17's four states) | 90 s at 51%: USD 0.3 / 3 / 30 / 304 | a deposit credited BEFORE the lock, which no conforming wallet does |
|
||||
| Sustained red-flooding of honest blocks (repeat 60-s withholds) | 45 to 51%: honest blocks 25 to 28% red, honest subsidy to the attacker, weight share 48 to 56%, chain reorganising every minute | the lock bounds each hold to under 63 s (the first checkpoint inside the hold locks by then); W2 counts blues | weight ceiling about 56% at 51% of hash, 77% at 67% (approx., 3.1): a 51% miner never reaches 2/3 | the ordinary cost of 51% | about a quarter of honest subsidy while it lasts |
|
||||
| Balance attack (keep two honest halves balanced) | needs network control, not hash: harness s3 and the redteam show a partition under merge depth heals to one chain (`redteam-2026-10-04.md` rows 2, 3) | merge depth 3,600 s merges the sides; beyond it, blue work and finality decide | one chain within merge depth; beyond it the 3.7 item 9 fork (section 4.3 finality) | 0 hash | nothing without a partition tool |
|
||||
| k-cluster poisoning (make honest blocks red) | the same as red-flooding: only a withholder can be in an honest block's anticone without being in its past; share bound as above | k = 18 at 1 bps (Kaspa's table, delay bound 5 s) | at d = 5 s a 34% withholder turns 29% of honest blocks red in a 30-s window (sim) | as 51% | redirected subsidy at 45%+ only |
|
||||
| Timestamp games on ordering | none on GHOSTDAG (order is by blue work and hash); on the clocks see 4.2 | 10-s future tolerance, parent minus 10 s (spec 02 2.3) | past-median time can run at most 10 s ahead of real time: nothing against a 30-day window | 0 | nothing |
|
||||
| Merge-depth games (release a chain forked over 3,600 s ago) | honest blocks of the hour become unmergeable and are abandoned if the released chain is heavier: the 229-block shape of 6 Oct (CLAUDE.md 6 Oct rules) | F1: a chain missing a certified checkpoint is not a candidate; any lock inside the hour kills it | only during a pause or the first 30 days; depth then bounded by the finality depth, 12 h | 1.5 h at 51%: USD 18 / 183 / 2k / 18k | an hour of honest subsidy orphaned, none gained |
|
||||
| Red-block flooding (publish blocks on stale parents) | the attacker's blocks are red, pay the honest merger, carry no weight; honest blues unaffected | W2, the red rule | pure loss to the attacker | | nothing |
|
||||
|
||||
### 4.2 The difficulty rule: the seven recorded ways and the ones to add
|
||||
|
||||
The seven of `sim/difficulty/attacks/README.md` and `results.md`, read not re-derived (Igneum rule v2 with the 4 October clock and floor):
|
||||
|
||||
| # | way | recorded bound | status |
|
||||
|---|---|---|---|
|
||||
| 1 | pool hopping (10 to 100% of the base, 24 h) | +1.5% blocks per hash at most, 0.7 points over Kaspa's rule; a 60-s dwell makes the 50 and 100% hoppers lose 1.7 to 4.0% | PASS under 5%, open by the letter |
|
||||
| 2 | pulsed rental (50x for 10 min hourly) | weight per hash 0.26 (Kaspa's rule 0.98): a pulse buys no weight; the base's blocks per hash fall 36% in the hour after | PASS (M14, F14 closed with the finality run: the renter never reaches a third) |
|
||||
| 3 | timestamp stretching (30 and 50% forger, earliest, latest, alternating) | +0.4 to +1.1% drift after an hour (worst seed +2.7%), difficulty ratio 1.00, worst gap 10 s; on 3 igneumd nodes a 50% forger moved nothing (0.82 to 0.88 blocks/s, 0 rejected) | FIXED (M23); before the fix the chain ran at a fifth of its rate at 9.9x difficulty |
|
||||
| 4 | short-lane oscillation (25% square wave every 120 blocks) | std 0.160 against 0.045 steady, 12% above the attacker's own square wave; the oscillator earns 1.4% less | FAIL by the letter, no past-only controller can pass, no change |
|
||||
| 5 | epoch games (hold dodger, hold flooders) | 0.0%, +0.7%, +0.3% (worst +2.4%) | PASS |
|
||||
| 6 | polluted window (10x joins and leaves at the lane switch) | settles 292 to 334 s, worst gap 17 s | PASS (Kaspa's rule 2,910 to 3,540 s) |
|
||||
| 7 | block flood (85 blocks/s of PoW-less input) | the target stops at 2^128 after about 2,630 blocks, no panic | FIXED (floor) |
|
||||
|
||||
Ways not in the seven, with the bound this lane gives:
|
||||
|
||||
| # | way | model | bound | who gains |
|
||||
|---|---|---|---|---|
|
||||
| 8 | Red-share gaming: a withholder turns honest blocks red, the estimator counts blue work only (spec 02 2.3 "what this section does not do"), the controller eases | the 60-s rows of 3.1: the blue rate read is 0.86 of true at 20%, 0.76 at 34%, 0.79 at 45%, 0.80 at 51%, 0.86 at 67% | the ease is at most 1/(blue share) - 1: 16 to 32% more blocks per real second for everyone (emission above schedule by the same factor, spec 02 2.5); no relative gain to the attacker beyond 4.1's redirected subsidy; the attacker's own reds cap it | nobody relatively; everyone's emission runs 16 to 32% fast while it lasts |
|
||||
| 9 | The star collapse (run A): effective delay past k / lambda, red to 67 to 77%, the controller reads a third of the rate and eases, the DAG widens | 3.3's table | a positive feedback with no attacker: the controller must read total work or the fleet must not be a star; lane 5 owns the rule, the fleet lib the topology | an attacker who can slow the hub (DoS) gets the collapse for free |
|
||||
| 10 | Clock trust after a pruning-proof sync: a header whose selected parent has no stored clock starts from the raw stamp (difficulty-2026-10-03.md section 11, Limits) | not measured | at most one window of bias after a sync; a forger needs to be the first blocks a syncing node sees | a stretcher against fresh nodes only |
|
||||
| 11 | Partition retarget: each side retargets to its share within 657 s (the 50x step-down figure), so each side keeps 1 block/s; at the heal the heavier side's targets rule and the lighter side's blocks carry less work | spec 02 2.3 measured steps; `finality_v2.py` +daa | consistent by construction; the minority's blocks merge red under merge depth | nobody |
|
||||
|
||||
### 4.3 The finality weight
|
||||
|
||||
| attack | what it achieves | defence | bound (sim) | rent | earns |
|
||||
|---|---|---|---|---|---|
|
||||
| Sybil (many keys) | nothing: weight is blue blocks, every draw is by weight (W6; harness s2: dust keys zero weight, sortition by weight PASS, F17 fixed) | W2, W3, F17 | 0 | 0 | 0 |
|
||||
| Weight capture by mining (the renter) | share (t/30) A: 1/3 on day 10/A, 2/3 on day 20/A, never under A = 2/3 (`results_v2.md` B to 0.04 points; sweep R) | the 30-day flat window | 51%: veto day 20, 2/3 never while honest miners stay; 67%: day 15 and 30; 90%: 11.1 and 22.2 | 20 d at 51%: USD 6k / 58k / 585k / 5.8M | 22M IGN of subsidy |
|
||||
| Weight capture by buying or renting keys | a bought key is worth its blocks and decays as the window slides: share = A (1 - t/30) + r t/30 (3.11.5; `results_v2.md` K; sweep K); keys worth 40% hold the veto from day 1 to 20, worth 20% never | W2 decay, W5 succession, equivocation strips a sold key the seller still holds | max(A, r) for a day, r after 30 days; a silent 40% buyer stalls 63k of 86k checkpoints then loses the veto on day 19 to 20 | the price of pools' keys, not hash | a pause of up to 20 days |
|
||||
| Long-range (private DAG from an old point) | a cold node with no certificate follows the heavier DAG (F5) | F5's trusted certificate (designed, not implemented); the client-shipped checkpoint (proposal 11) | needs more blue work than the public DAG over the window: 30 days of >50% | USD 9k / 88k / 877k / 8.8M | 34M IGN |
|
||||
| Eclipse (poisoned pool) | 0 conflicting locks, 0 locks on the eclipsed side at 1, 2, 4 h for a 34% attacker and a 20% pool (`results_v2.md` L3, F2); sweep E extends it to 51 and 67% | the 2/3-of-total floor binds whatever the presence window says (3.3.2) | the eclipsed side must hold 2/3 of total: a 47%+ attacker plus a 20% pool (sweep E, see 3.5) | the eclipse plus the weight | nothing under the bound |
|
||||
| Partition with an equivocator | 0 conflicts to 33%, conflicts from 34% (`results_v2.md` H at 2/3; M5 under v3); sweep P at 51, 67, 90 | two certificates need 4/3 of weight in signatures (3.11.2) | 1/3 of weight, every view, any partition length under one window since the last lock (v3) | the 20-day veto | two finalised histories across a partition, each side's deposits |
|
||||
| Equivocation alone | strips the key for 30 days, no coin penalty (F6); detection by any carrier block, agreed by every node (F23 fixed) | 3.6 | costs the attacker its weight, nothing else | | nothing |
|
||||
| The 30-day window edges | (a) the frozen table expires at exactly day 30.00 after the last lock: both sides of a long split lock alone at once (M3); (b) a departed set leaves the sliding table over 30 days and the frozen one at the cliff (M4); (c) the first 30 days have no lock at all (3.8, `min_daa` = window); (d) new honest cohorts are under-weighted t/60 for 30 days (G) | stated in 3.7 items 2, 7, 9 | a partition or departure longer than one window ends with the fork of 3.7 item 9 and a manual F5 | | |
|
||||
| The pause as a liveness attack | a silent set at or above 1/3 pauses every lock for as long as it stays silent (J, L1; sweep S) at zero marginal cost since it keeps earning | none in the rule; the node reports the pause; exchange guidance treats the chain as PoW with a 12-h depth | the 1/3 veto: 20 days at 51% | 0 once held | nothing directly; enables the 12-h PoW double spend below |
|
||||
| What an attacker can do during a pause | plain proof of work: reorg up to the finality depth 43,200 DAA (12 h) with a heavier chain; beyond merge depth the honest blocks are abandoned (the 229-block shape); every certified checkpoint before the pause still binds | finality depth; the exchange guidance of 3.9 | 12 h of >50% hash | USD 146 / 1.5k / 15k / 146k | a deposit credited at the PoW depth; 559k IGN of subsidy as a miner |
|
||||
| Tonight's departure (confirmed, lane 3 `finality-and-weight.md` 3.1 and 4.1) | 20 keys holding 42.7% of the frozen table stopped mining 18:27 to 18:30Z (the rehearsal job); the last lock 6842 at 18:39:40Z; 6843 determined with 53.1% of total signing and never locked; under v2 the stayers' sliding share crossed two thirds at 6912 (19:14:53Z, a 35-min pause) but Q5 held them at 57.3% of the frozen table; expected first lock when that table expires at DAA 216,402, about 20:40Z, or when 9.4 points of departed keys return. Sweep C agrees: 51% leaving pauses 10.5 days (v2) or 30.0 days (v3) at mainnet scale; at tonight's 46.9% (observer's view) 7.7 days under v2, 30 under v3 (lane 3, 4.1) | by design (F21: the project lead chose the pause over the fork); a view cannot tell a departure from a partition | anything over 1/3 of the table leaving at once pauses finality for a window | 0 | 0; what an attacker can do during it is the row above |
|
||||
|
||||
### 4.4 Miner signalling (P2)
|
||||
|
||||
| game | model | bound | price |
|
||||
|---|---|---|---|
|
||||
| 6% holdout blocks a change for ever | the window share has 0.07 points of noise: 94.9% flips with P 0.09, 94.5% never | the floor N6 ends it; nothing else does | USD 18 a day at 1 GH/s, 17.9k at 1 TH/s; the holdout earns 6% of subsidy meanwhile, so net about zero at equilibrium |
|
||||
| What the floor does | converts the signal into a fixed height at N6: the hazard of 6 October (DAA 198,000 crossed while boxes were still updating: two-sided chain, 229-block reorg) returns for every node not on the object at N6 | the floor should sit no nearer than a week past the publish on a network miners run, and the stale-box list (P1 pass rule) must be empty before it | |
|
||||
| Signal then defect | a defector's blocks fail PoW under the new program and are refused (`check_header_version` then PoW); a pool with stale workers loses their blocks | the defector pays, nobody else | |
|
||||
| The one-day window | a renter at 19 N for 24 h with a patched byte forces the flip; for v4 it hurts nobody (the signalling binary is the v4 binary), for a later object it forks every node still on the old one | the share of the fleet not yet on the object at the forced flip | USD 5.3k at 1 GH/s to 5.3M at 1 TH/s, and the market could not supply a TH/s |
|
||||
| Proposed | require 95% on each of 7 consecutive daily windows (7x the renter's bill, a week of visible share), keep the one-day tally for display | | 3 hours |
|
||||
|
||||
### 4.5 Proof records
|
||||
|
||||
| attack | today's rule | bound | who gains |
|
||||
|---|---|---|---|
|
||||
| Forgery of state | the native-execution veto: a record whose statement differs from the node's own execution of that segment along the carrying block's chain is ignored (7.2 item 5, P11 fixed) | state is never moved by a record; a soundness bug is a light-client problem (P7), a job-output problem for the precompile (D6, contained by R12) | nobody |
|
||||
| Forgery of the proof (correct statement, random bytes) | NOT checked in consensus (7.7 item 4, 7.8 item 8); the first valid record per shard or segment carried pays | a producer at share H takes at least H of the 20% pool and, since its fake rides its next block while an honest proof takes 9 to 11 s on a 5090 (P9 table), most shards outside the 10-s exclusive window; inside it only the H of slots it is assigned | any block producer: 11,636 IGN/h at 51% of a pool paying 22,815 IGN/h |
|
||||
| Withholding (an assignee sits on its window) | after 10 DAA s anyone may prove and be paid; a segment unproven after 600 DAA pays nothing (7.8 item 7); mandatory proofs off | one window of latency per absent assignee; nothing waits | nobody |
|
||||
| Grief (flood the pool with invalid records) | 6,000 invalid records: 0 accepted, 0.31 to 0.68 ms each, node up (redteam row 10) | CPU per record | nobody |
|
||||
| The aggregator naming itself as every prover | provers committed in the proof's public values and checked (P12 fixed) | 0 | nobody |
|
||||
| Record ordering race | the first valid record carried wins: a producer can front-run honest provers' records in its own block (the forgery line) | fixed by consensus verification (rank 1), then by aggregator sortition (O-7.3) | |
|
||||
|
||||
### 4.6 The execution layer
|
||||
|
||||
| attack | today's rule | bound | note |
|
||||
|---|---|---|---|
|
||||
| Snapshot poisoning over p2p | `p2p_snapshot_gate` refuses tip 0, below the restart, at or below the own tip, and anything while the executor runs unblocked; a snapshot whose state at the restart block differs from `exec_restart_state_root` is refused (release-0.3.14.md) | a wrong state ABOVE the restart block is not detectable by the node: headers commit to no execution root (spec 02 2.6: blocks carry transactions only), certificates sign (chain id, index, block hash) only (C2) | the poisoned node's native statement then disagrees with every carried record, it pays nothing and sees every honest record as invalid; the signal exists but nothing reads it as an alarm |
|
||||
| The pin | `exec_restart_number / hash / state_root / trust_daa` arrive by the signed manifest on the devnet (the reddit review's admin-key finding, 1.6 item 3); on mainnet no manifest exists, so the pin is genesis-only or absent | a release-key holder sets execution state on the devnet; on mainnet the same power would need the 95% signal | disclose (the review's key-powers table) |
|
||||
| Deep reorg never resets execution | a reorg reloads the newest persisted generation at or below the fork (ring 2,048) else blocks loudly and asks a peer; never a genesis replay on a pruned node (0.3.14) | under active finality a reorg is bounded by the lock (90 to 120 s), far inside 2,048; during a pause the 12-h depth is 43,200 blocks, 21x the ring, so a pause-time deep reorg blocks every pruned executor until a peer's snapshot arrives, which is the poisoning path above | the ring should reach the finality depth (proposal 12) |
|
||||
| Duplicate and nonce games, pgas bombs, malformed bodies | exec-attacks suite 96 of 97 checks, every executed block under B_p, an over-budget transaction refused at the mempool with the pgas metered (redteam row 28) | per block B_p of proving gas and B_e of execution gas; an aborted transaction pays | |
|
||||
|
||||
### 4.7 Peer to peer
|
||||
|
||||
| attack | what it achieves | defence | bound | price |
|
||||
|---|---|---|---|---|
|
||||
| Eclipse of one node | feed it a private chain: its difficulty eases to the attacker's hash within about 11 min (the 50x step-down takes 657 s), so 1% of the network's hash produces a plausible 1 block/s chain for the victim within 20 min; it sees no certificates and reports `finality_active` false | the exchange guidance (treat a pause as PoW with a 12-h depth); a node that holds locks will not follow a chain missing them (F1) | a victim that follows its node's finality flag loses nothing credited under a lock; one that credits at a PoW depth is Kaspa's or Monero's eclipse victim | a few IPs |
|
||||
| Eclipse or outage of the hands | tonight's pause was NOT this (lane 3, 3.1: locks 6824 to 6842 formed with the hub down, the zero-aggregator fallback carried 64 of 251 certificates); the attack stands in general: a fleet that peers only through two hosts is a star, and a star with its centre down is a partition into n islands, each under 2/3, finality paused until the heal; longer than merge depth (60 min) it is the 3.7 item 9 fork | aggregator fallback (any node aggregates after 15 DAA s); votes ride in blocks (Q2); neither crosses a dead hub | pause for the outage; proposal 4 (peer floor) removes the star | 0 hash: the DoS of two hosts |
|
||||
| Crash a pruned node from any peer (main, 19:57Z: the hub, a pruned 0.3.14 node, panicked on an `unwrap` over `KeyNotFound` at the devnet genesis when a re-joining peer synced below its retention; `consensus/src/processes/sync/mod.rs:87`, fix in 0.3.15) | any peer takes any pruned node down by asking for history it does not hold | none today; the rule: no `unwrap` or `expect` on a path a peer's request reaches, a `SyncManagerError` instead, and a fuzz of the sync request space (locator low/high, antipast low/high, missing-bodies high, pruning-point anticone) against a pruned node as the CI gate | zero hash; one request per node; repeated, a liveness attack on every pruned node (every mainnet node prunes) | 0 |
|
||||
| Eclipse of the seeds (3 Hetzner DNS seeds) | a fresh node bootstraps into attacker peers and, with no trusted certificate, follows their DAG (F5 cold start) | none implemented; F5 designed | the long-range attack's price (4.3) for the DAG, zero for the eclipse | USD 9k to 8.8M for the DAG |
|
||||
| Handshake refusal (params digest) | a peer with another digest is refused before any flow (X18); an attacker cannot make honest peers refuse each other | it is a defence; the only cost is the digest-less allowance still open on devnet and simnet | 0 | |
|
||||
| The p2p snapshot path | 4.6 | | | |
|
||||
| Memory under flood | 269 to 780 MB per minute of flood on 0.3.4 (M30), bounded since 0.3.5 to the record window plus pruning depth | | | |
|
||||
|
||||
Siblings of the 19:57Z crash class, read-only grep of the fork (main checkout `vendor/igneum-node`, 6 Oct) for `unwrap` and `expect` on paths a peer's request reaches. The sync manager's entry points are called from the request flows (`protocol/flows/src/v10/request_headers.rs`, `request_antipast.rs`, `request_block_locator.rs`, `request_ibd_chain_block_locator.rs`, `request_pruning_point_and_anticone.rs`, `request_block_bodies.rs`) through `consensus/src/consensus/mod.rs:1341` (`get_hashes_between`), `:1624` (`get_missing_block_body_hashes`), `:1647` (`create_block_locator_from_pruning_point`):
|
||||
|
||||
| file:line | what panics | reached by |
|
||||
|---|---|---|
|
||||
| `consensus/src/processes/sync/mod.rs:87, 88` | `ghostdag_store.get_blue_score(low/high).unwrap()`: the hub's crash when `low` is below retention | `antipast_hashes_between` from a peer's antipast or headers request |
|
||||
| `sync/mod.rs:94` | `ghostdag_store.get_data(current).unwrap()` on the forward chain walk | the same |
|
||||
| `sync/mod.rs:117` | `find_highest_common_chain_block(...).expect("because of the pruning rules such block has to exist")`: false once the peer's `low` is pruned | the same |
|
||||
| `sync/mod.rs:123, 125, 130, 135, 148` | pruning point, selected-chain tip and index lookups `.unwrap()` | `create_virtual_selected_chain_block_locator` from a peer's locator request |
|
||||
| `sync/mod.rs:162, 172, 182, 194, 196` | status lookups `.unwrap()` along a peer-named `high` | `get_missing_block_body_hashes` from a peer's IBD blocks request |
|
||||
| `sync/mod.rs:211, 221` | `get_blue_score(low)`, `get_compact_data(current)` `.unwrap()` | `create_block_locator_from_pruning_point` from a peer's IBD chain locator request |
|
||||
| `protocol/flows/src/v10/request_headers.rs:98` | `hashes.last().expect("caller ensured ...")` | the headers request flow, after `get_hashes_between` |
|
||||
| `protocol/flows/src/ibd/negotiate.rs:40, 47, 112, 166, 172` | `locator_hashes.last().unwrap()` on a locator the PEER sent | the IBD negotiation (the syncee side, a hostile syncer) |
|
||||
| `protocol/flows/src/ibd/flow.rs:307, 361, 436, 440, 700, 717` | `async_get_header(...).unwrap()`, `async_validate_pruning_points(...).unwrap()`, `pruning_points.last()/first().unwrap()` on peer-sent pruning points | IBD against a hostile syncer |
|
||||
|
||||
The rest of the hits in those files are test code (`request_headers.rs:199 to 222`, `request_pruning_point_and_anticone.rs:197 to 280`, `trusted_data.rs:81 to 149`, `proof.rs:144 to 230`) or channel sends. Bound of the class: zero hash per crash; every pruned node on the network can be taken down by one request each, repeatedly, which during a pause or the first month is a liveness attack on the chain itself and at any time on the hands. Proposal (lane 1, 4 hours): convert the sync manager's unwraps to `SyncManagerError` variants, make the negotiation and IBD paths return `ProtocolError` on a missing block, and add `tools/ci` fuzz `sync-request-fuzz` that drives the six request flows with random and below-retention hashes against a pruned fast-time node and fails on any exit; gate: 10,000 requests, node alive, 0 panics.
|
||||
|
||||
### 4.8 The harness run (real DAG, 0.3.14 binary, rule v3, fast time)
|
||||
|
||||
`tools/finality-attacks/run.mjs s6 --fast-time`, SCALE 0.4, rule v3 forced on (`finality_v3_activation_daa` 0), the 0.3.14 Mac binary (`vendor/igneum-node/target-0314/release/igneumd`, built 6 Oct 17:56) and its `igneum-miner` (`vmine`), ports 29800 to 29812, suffix 980, `/tmp/igneum-horizon-fin`, run lock held 19:53 to 20:03Z. The 0.3.14 node refuses the master copy of `infra/fast-time/override-60x.json` (unknown fields `program_class_v4_activation_daa` and `program_class_v4_signal_window_daa`, which only the `ca3-v4-node` binary knows), so the harness ran from a scratch copy of `tools/finality-attacks` and `infra/fast-time` with those two fields removed; nothing tracked was edited. Six vmine voters at 6 blocks/s in all (3 per side), window 120 DAA, warm 168 s, split 60 s, heal 60 s.
|
||||
|
||||
| scenario | measured | reading |
|
||||
|---|---|---|
|
||||
| A, 3/3 split | window DAA ~1,019 at the cut, 6 voters, max locked 34 on both sides; new locks during the 60-s split 12, the first on each side at 30 s; locks resumed after the heal; conflicting certificates 9 / 11 after the heal | the recorded F21 shape at this scale: a side at 3 blocks/s advances its own DAA 3 a second, so the frozen table (one window of 120 DAA after the last common lock) expires 30 to 40 s into the split and the sliding table then locks each side alone, exactly as the redteam's row 18 (36 and 49 s) and the simulator's M3; the harness criterion (`zero new locks`) asserts more than the rule promises past one window |
|
||||
| B, 4/2 split | window DAA ~900, max locked 30; the 4 side locked 30 to 38 (first at 33 s), the 2 side stayed at 30 for the split; conflicting certificates 1 / 12 after the heal | the 4 side holds 4/6 = two thirds of the frozen table and locks as Q3 allows (inclusive); the 2 side locks nothing while the table stands; the post-heal conflicts are the 2 side's late solo locks after its own table expired (redteam row 18 saw the same 4 late locks), the F21 residual |
|
||||
|
||||
What it adds to the sim: the real DAG at fast time reproduces the frozen-table bound within its own DAA clock and the v2 control's shape (`split50-v2.md`: 3 solo locks at 126 s, 4 conflicts); nothing contradicts the simulator. What it does not test: a withholding attacker (vmine has no withhold flag) and any run longer than the window.
|
||||
|
||||
## 5. Model: the formulas and what holds
|
||||
|
||||
### 5.1 The ordering race against the lock
|
||||
|
||||
Inputs: lambda blocks/s (measured 1 on the devnet), k = 18 (cited, Kaspa's table), d (measured 0.34 to 2.3 s), checkpoint interval I = 30 blue score and depth d_cp = 60 (designed), lock latency after determination median 2.5 s, p99 4.6 s (simulated, `results_v2.md` A), 0.8 to 1.08 s on the test network (measured). A deposit at time 0 lies under the checkpoint block mined at most 30 s later, which locks at most 30 + 60 + 3 = 93 s later. An attacker forking before the deposit must win the race before that lock (after it, F1 excludes its chain). From 3.1: wins need H >= 1/2 for any T, or T < k H / ((1 - 2H) lambda) below it; at T = 90 s that is H >= 0.45 with Poisson noise (70 to 85 percent at 45 to 51 percent, 5 to 10 percent at 34 percent). Hence: a majority reorders at most the lock latency; a 34 percent miner at most about 60 s; a 20 percent miner at most the last k blocks. The honest guidance (credit on the lock) makes every case a zero.
|
||||
|
||||
### 5.2 Weight
|
||||
|
||||
share_renter(t) = (t/30) A (verified, B, sweep R); share_buyer(t) = A (1 - t/30) + r t/30 (verified, K, sweep K); two certificates need 4/3 of total in signatures, so the equivocator bound is 1/3 in every view (3.11.2, H at 2/3, M5 under v3); a partition side's own table reaches 2/3 on day 30 (2/3 - s)/(1 - s) under v2 (L4) and never before day 30 under v3 (M2, M3); a departed share x pauses 30 (1 - 1/(3x)) days under v2 and 30 days under v3 (L2, M4, sweep C). The weight ceiling of a withholder is 3.1's w(H) with the 60-s rows.
|
||||
|
||||
### 5.3 Rent
|
||||
|
||||
cost = A/(1 - A) x N x hours x 11.7 USD (measured price); earned = 0.8 x A x 31.688 x 3600 x hours IGN (spec 02 2.5); N_eq = 0.8 x 31.688 x 3600 x P / 11.7 GH/s at IGN price P (assumption inputs). At N_eq the veto nets about 0.48 x 480 h of subsidy and locking alone 0.33 x 720 h.
|
||||
|
||||
### 5.4 What holds, what breaks
|
||||
|
||||
| claim | holds | evidence |
|
||||
|---|---|---|
|
||||
| No hash share under 2/3 of weight reverses a certified checkpoint | yes | 3.11.2, H and M5 (0 conflicts under 1/3 in every seed), the DAG sim (the race ends at the lock) |
|
||||
| 51% of hash never reaches 2/3 of weight while honest miners stay | yes, with margin: 56% ceiling under red-flooding (approx.), 51% honest | B, sweep R, 3.1 |
|
||||
| A majority cannot forge a proof the nodes re-execute | yes for state; NO for payment: the proof itself is not checked and the pool is capturable | spec 07 7.7 item 4, 7.8 item 8 |
|
||||
| A rule changes only at 95% signalling | yes for the signal path; the floor is a fixed height that can cross with stale nodes | P2, signalling.py |
|
||||
| A majority loses more than it earns | for the veto and the lock-alone, yes (net 48% and 33% of the period's subsidy at equilibrium); for the pause-time 12-h double spend and the fake records, NO | cost_model.py |
|
||||
| The pause is safe | safe for history, costly for liveness, and buyable at zero hash by taking the hub down | tonight; sweep S and C |
|
||||
|
||||
## 6. Ranked proposals: the defences we do not have
|
||||
|
||||
| rank | proposal | evidence | model | hours | consequence per tier | gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Verify the aggregated segment proof in consensus (a record whose proof does not verify against the pinned aggregator key is invalid; the per-shard v0 record stays payout-only until then and is capped at the exclusive window) | 4.5: a producer captures H to most of the 20% pool with fake records; P21 "stated, not fixed" | capture = pool x (H inside the window + most outside); verification cost per record = SP1 light verifier (P3: 1.3 to 2.1 s setup, then per-proof ms, to measure) x 2 records per block | 8 to 12 | home miner (8/12/16 GB): its honest shard records are paid, not front-run; rig and pool: proving income real; prover: the market is honest; holder: 20% of emission is not a miner's bonus; rollup customer: a paid proof is a verified proof; node: +verify CPU per block | fast-time: a correct-statement fake record is refused, an honest one paid, p95 block validation under 50 ms with 2 records |
|
||||
| 2 | Weight-gated deep fork choice: among candidate tips, a tip whose fork point is older than D (say 10 min of past-median time) is a candidate only if the blocks on it since the fork were produced by keys holding at least 1/3 of the weight table at the fork point (a function of the block's past, deterministic) | 4.3 "during a pause": a renter with fresh keys double-spends at the 12-h depth for USD 146 at 1 GH/s; the DAG sim's 90-s race | rented hash has zero weight for 10 days (W2), so its deep chain is never a candidate; honest partition sides over 1/3 keep today's behaviour; a side under 1/3 cannot reorg the other past D, which is the desired outcome | 10 to 16 (virtual processor candidate filter + weight-at-fork from the finality tables) | home miner and rig: nothing changes; pool: nothing; holder and exchange: a pause-time or first-month deep reorg needs 1/3 of weight, 20 days in public, not 12 h of rent; node: one table lookup per deep candidate; rollup customer: PoW-depth credits become weight-backed | fast-time: a fresh-key renter at 3x the hash forking 2 min back is refused for ever; a 40%-weight honest side forking 2 min back is adopted |
|
||||
| 3 | Vote-or-burn: a block whose producer key has participation under 0.5 over the presence window in the block's own past burns 20% of its producer share | 4.3 "the pause as a liveness attack": zero marginal cost | the attacker's pause then costs 0.2 x A x 0.8 x 114,077 IGN/h: 7,757 IGN/h at 34% (USD 155/h at 0.02); partition-safe because the test is the block's own past (a side's keys vote their own checkpoints); honest outages (2.2%) leave participation above 0.9 | 6 to 8 (coinbase rule + the finality manager's participation count at the block) | home miner under dust: not a voter, counts 1, unaffected; a miner whose node never revealed a vote key: loses 20% until it does (an incentive); pool: votes or pays; holder: silent weight stops being free | fast-time: a 40% silent set's coinbases shrink 20%, honest ones do not, both sides of a 3/3 split unaffected |
|
||||
| 4 | Peer floor and mesh for the fleet and the node: the fleet lib dials at least 3 other boxes beside the hands; the node logs an alarm and the app shows it when outbound peers fall under 3 or when no vote has been received for 2 checkpoints | run A's star (321 tips, 77% red); the hands as the fleet's only peers (lane 3 refuted this as tonight's cause; the shape stands) | a star with its hub down is n islands; with 3 extra peers per box the graph stays connected under any single failure | 2 to 4 (fleet lib) + 3 (node alarm) | every tier: finality stays up when a hand dies; home miner: sees "no peers" instead of a silent pause | Devnet 2: kill the hub for 10 min, locks continue; the alarm fires on the known-bad case and not on the known-good |
|
||||
| 5 | Vote over the execution root too: the vote signs (chain id, index, block hash, post_root of the checkpoint's segment); a certificate then pins the state, snapshots are checked against the last certificate, and the poisoned-snapshot node cannot join the quorum | 4.6 snapshot poisoning: a wrong state above the pin is undetectable | every voter is a full node that executes natively (spec 07); the cost is exec lag added to lock latency (the executor runs seconds behind the tip) | 8 to 12 | holder and exchange: a certified checkpoint carries its state; rollup customer and light client: one object says ordered and executed; node: a divergent executor is visible at once; miner: a vote waits for its executor (lock latency + exec lag) | fast-time: three nodes agree; a fourth with a tampered snapshot votes a different root and is outvoted, alarm raised; lock latency rises by the measured exec lag only |
|
||||
| 6 | Signalling over 7 consecutive daily windows, floor no nearer than 7 days past the publish, the stale-box list empty before the floor | 4.4 | the renter's bill x7; a week of visible share | 3 | pool and rig: a week more before a class change; home miner: a week to update | fast-time gate: 6 of 7 days at 95% does not flip; 7 does |
|
||||
| 7 | Detector-driven alarms: the share-pattern detector (counter-asic-3-status, Detector row) and the finality flag feed one node-side `security_alert` (correlated group over 1/3 of a day's blue blocks; pause; conflict) that wallets and the explorer show as "confirm at 12 h" | 4.3, the exchange guidance exists only as text | an alarm when any single party crosses the veto line in public, which the rule says takes 10 to 20 days | 4 to 6 | holder and exchange: a number to act on; home miner: the app shows the chain's state | fast-time: a 34% silent set raises the alarm within 5 min; the honest run never does |
|
||||
| 8 | The signed departure (LEAVE: lane 3's rank 1, `finality-and-weight.md` section 6; a `leave` item carried in blocks, the key out of every denominator one hour after inclusion, sent by the app and the fleet library on a clean stop) and F5's trusted certificate implemented | tonight's departure (lane 3, 3.1): 42.7% left in three minutes and the frozen table held finality for a window; a view cannot tell a departure from a partition, so no automatic rule re-enables locks without reopening L4/M3 | stripping lowers total; an attacker stripping stolen honest keys is K's bound (needs keys worth 1 - a/(2/3)); lane 3's sim T: first lock 1 h after a 34 to 50% departure, 0 conflicts in every partition row | 6 (lane 3) + 4 | pool and rig: an orderly stop keeps finality up for everyone; holder: no 30-day pause after a planned fleet move; node: the operator's certificate for the disorderly case | fast-time: 45% of weight stops with exits, locks continue; without, the pause |
|
||||
| 9 | Client-shipped checkpoint: each release carries the latest certified checkpoint (index, hash) and voter-table digest; a cold node refuses a DAG missing it (assumevalid's shape) | 4.3 long-range, 4.7 seeds | the long-range attack must then out-work the public DAG since the release, not since the window | 3 | every tier: a fresh install cannot be bootstrapped onto a private DAG; trust is the release key already trusted for the binary | a cold node offered only a private heavier DAG refuses it |
|
||||
| 10 | Exec generations spaced geometrically to the finality depth (1, 2, 4 ... 43,200 blocks: about 16 generations) so a pause-time deep reorg never needs a peer's snapshot | 4.6 | 16 x state size on disk (about 1.8 GB today, measured 114.8 MB per snapshot) | 3 | node operator: disk; every tier: no blocked executor after a deep reorg | fast-time: a 5,000-block reorg re-executes from a generation, no snapshot request |
|
||||
| 11 | Checkpoint anchoring to proof records (the certified checkpoint's hash as a public value of the next aggregated proof; the verifier checks the certificate natively) | asked by the brief | buys light clients and bridges one object (certified and proven); changes nothing a full node does, since the proof chain already commits to block hashes and a reorg already needs new proofs; in-circuit BLS is 40+ hours and not worth it | 6 (public-value commit) | rollup customer and light client: one verification; others: nothing | a light client verifies a proof carrying a certificate hash and the certificate |
|
||||
| 12 | Prover attestations as a second finality leg (a lock also needs proof records from a quorum over the checkpoint) | asked by the brief | NOT recommended: provers are the miners (same vote keys), so no new party; proving covers 2.4% of blocks today and the pool is "not active" (reddit review 1.4), so every lock would wait on proofs and finality would pause constantly; what it would add (execution validity) rank 5 gives without the liveness cost | 8 if ever | every tier: lock latency becomes proof latency (20 to 60 s target, minutes today) | only once coverage is 100% and rank 1 is in |
|
||||
| 13 | Time-locked (vesting) weight | asked by the brief | NOT recommended: a bought key transfers vested weight, so K's bound is unchanged; honest new cohorts wait N days longer than G's 20 | 3 if ever | new home miners: later vote; attacker: unchanged | none |
|
||||
| 14 | Any rule that keeps finality on after a large honest set leaves abruptly without a signed exit | asked by the brief | NOT possible safely: departure and partition are the same observation in one view; re-enabling locks under the frozen table reopens the double lock of L4 and M3 at the same day; the honest options are rank 8 (exit) and a shorter frozen expiry, which trades the partition bound one for one | 0 | | |
|
||||
|
||||
One paragraph each on the two that matter most.
|
||||
|
||||
Rank 1, proof verification in consensus. Today a record is paid on a signature and a statement match; the statement is computable by every node, so a producer writes the right statement, random proof bytes and its own payout address into its own coinbase and is paid the shard or the aggregator share. The exclusive window limits it to the slots it is assigned (by weight, so H of them) for 10 DAA s; after that the first record carried wins, and the producer's block is first. The only thing that stops it is a verified proof as a condition of payment. SP1's light verifier exists (`igneum-prove-host --mode verify-segment`); the cost to measure is the per-proof verification time on the validation path, and if it is over a few tens of milliseconds the aggregated record (2 per block) is the one to verify in consensus while the per-shard record stays payout-only inside the window. Consequence per tier: an 8 GB home miner that proves on the patched prover is paid for what it proves; a pool's proving income is real; a holder's 20 percent of emission goes to proofs.
|
||||
|
||||
Rank 2, weight-gated deep fork choice. Finality's whole argument is that weight cannot be rented; fork choice today ignores weight, so during a pause or the first month a renter's heavier chain reorganises up to 12 hours. The rule: a candidate tip whose fork point is more than D of past-median time behind the node's selected tip is a candidate only if the keys that produced its chain blocks since the fork hold at least a third of the weight table at the fork block (the same `voters_at` the finality manager computes). It is deterministic (a function of the DAG), it leaves every reorg under D to GHOSTDAG as now, it leaves honest partition sides over a third exactly as now, and it makes the pause-time double spend cost the veto (20 days in public) instead of 12 hours of rent. What it costs: a side of a partition under a third of weight that is heavier by work cannot reorganise the other side past D at the heal, which is the outcome the certificate would have produced anyway; and a cold node with no table yet follows F5. Gate: the fast-time run in the table.
|
||||
|
||||
## 7. Open questions and what could not be run
|
||||
|
||||
| item | why |
|
||||
|---|---|
|
||||
| The star's effective relay delay | the model needs the hub's measured relay time at 12 blocks/s to 41 peers; main's run A rows give the outcome (77% red, reorg 55) and this lane gives the curve (3.3); the fleet measures the delay |
|
||||
| The finality sweep's R row at 90% | the renter's 9x hash makes 905 honest keys fall under dust in the model (B's dust effect), so the simulated share overshoots the formula by 1 to 2 points, as B recorded |
|
||||
| Proof verification time on the validation path | not measured; P3 gives setup only; rank 1's hours depend on it |
|
||||
| Weight-gated fork choice against the C4 certificate-driven reorg | a certificate over a deep block must still force the reorg (3.5); the gate must exempt certified tips; not modelled |
|
||||
| The DAG simulator has equal work per block, no difficulty, no bodies | a withholder also controls its blocks' timestamps and the attack-side difficulty; the 10-s rules bound the clocks, the DAA lane bounds the rest |
|
||||
| igneumd harness | one s6 run on the 0.3.14 Mac binary (4.8); the node-side numbers for the other scenarios are the recorded runs cited |
|
||||
| The live pause's end | lane 3's arithmetic (4.1): the frozen table of lock 6842 expires at DAA 216,402, about 20:40Z, unless departed keys holding 9.4 points of it return first; main reads the chain |
|
||||
|
||||
## 8. Summary paragraph
|
||||
|
||||
The ordering layer and the lock together bound a hash majority to the lock latency: the DAG simulator shows 45 to 51 percent winning the 90-second race 70 to 85 percent of the time and nothing beyond it, 34 percent winning only inside 60 s, 20 percent only the last k blocks; weight cannot be rented faster than 10 days per third, bought keys decay as the window slides, no equivocator under a third splits finality in any view, and the costs in rented hash are USD 6k to 5.8M for the veto and 17k to 17M to lock alone across 1 GH/s to 1 TH/s, of which the attacker earns back half as subsidy. Three things a hash majority does buy today: the proving pool, by writing fake records into its own blocks (the one line that earns more than it costs); a 12-hour proof-of-work double spend during a pause or the first month for USD 146 to 146k; and a pause needs no attacker at all, since a planned 43 percent departure caused tonight's and the frozen table holds it for a window (lane 3, 3.1). The three findings as numbered lines:
|
||||
|
||||
1. A 51 percent withholder reorganises at most the lock latency (90 to 120 s; 32 to 46 chain blocks at 1 block/s, 80 percent success), reaches a weight ceiling of about 56 percent by red-flooding (never two thirds), and costs the honest side a quarter of its subsidy while it lasts (`ghostdag_sim.py`).
|
||||
2. The veto costs 20 days of 51 percent in public (USD 6k at 1 GH/s, 5.8M at 1 TH/s, half earned back) and then holds a pause for free; a pause-time 12-hour double spend costs USD 146 to 146k (`cost_model.py`, `finality_horizon.py` S and C); weight-gated deep fork choice (rank 2) makes it cost the veto instead.
|
||||
3. Any block producer captures from H to most of the 20 percent proving pool today with correct-statement fake records (11,636 IGN an hour at 51 percent), because consensus does not verify the proof (spec 07 7.7 item 4); proof verification in consensus is rank 1.
|
||||
|
||||
## 9. Files and how to run them
|
||||
|
||||
| file | run |
|
||||
|---|---|
|
||||
| `sim/horizon/consensus-security/ghostdag_sim.py` | `with-lock.sh run nice -n 19 python3 sim/horizon/consensus-security/ghostdag_sim.py --seeds 20 --out ghostdag_results_1bps.md`; `--bps 10 --k 124 --delays 0.35,0.67,2 --holds 10,30,60,90 --warm 60 --post 15` for the 10 bps grid |
|
||||
| `sim/horizon/consensus-security/finality_horizon.py` | `with-lock.sh run nice -n 19 python3 sim/horizon/consensus-security/finality_horizon.py --out finality_horizon_results.md` (imports `sim/finality_v2.py`) |
|
||||
| `sim/horizon/consensus-security/signalling.py` | `python3 sim/horizon/consensus-security/signalling.py --out signalling_results.md` |
|
||||
| `sim/horizon/consensus-security/cost_model.py` | `python3 sim/horizon/consensus-security/cost_model.py --out cost_results.md` |
|
||||
| results | `ghostdag_results_1bps.md` and `.json`, `ghostdag_results_10bps.md` and `.json`, `finality_horizon_results.md`, `signalling_results.md`, `cost_results.md` in the same directory |
|
||||
356
docs/analysis/horizon/economy-and-utility.md
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
# Horizon lane 4: economy and utility. What IGN is for beyond gas, the 80/20 under stress, ten years without a treasury, the dev fee, miner signalling, and what the other chains got wrong
|
||||
|
||||
6 October 2026, evening UK. Lane 4 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon`). Every model is in `sim/horizon/economy-and-utility/` with a README line per script; every dollar figure names its inputs and its label. Nothing here is a price prediction, an offer to sell anything, or a change to any consensus parameter.
|
||||
|
||||
**Read:** `docs/spec/05-fees-and-economics.md` (whole, 5.10 and 5.11 included), `02-consensus.md` 2.5, `07-execution.md` (7.2 to 7.8); `docs/design/payment-routes.md`, `developer-adoption.md` (2a to 2c), `execution-layer.md` (4.3 to 6), `miner-dev-fee.md`; `docs/plans/funding.md` 1 to 5; `docs/analysis/security-budget.md`, `economy-2026-10-04.md`, `base-fee-floor.md`, `prover-floor.md`, `prover-tiers-real-cards.md`; `sim/economy/` (README, sim.py, security_budget.py, results.md, levers.md); `docs/commercial/prover-customer-brief.md`; `docs/fud-ledger.md` E1 to E8 (E9 to E18 are cross-referenced from the spec and the status updates, the file carries E1 to E8 as sections), P6, P8, P9, P10, P14, P22, D1 to D6, C9, C10, G8, L6, L7; `site/litepaper.html` Building, Economics, Governance; `docs/bench-log.md` lines 1226 (the dev fee measured), 1910 (proving v1), 2349 (aggregation cost), 2582 (rental cost of hash); `docs/plans/counter-asic-3-node.md` section 6 (the P2 rule) and `counter-asic-3-status.md` P2; `docs/analysis/horizon/frontier.md` section 0 (the ranking) and 3.1, 3.2, 3.5, 3.6, 3.11 to 3.13; lane 3's `sim/horizon/consensus-security/cost_results.md` and `signalling_results.md`; `vendor/rusty-kaspa` (main checkout) `consensus/core/src/config/params.rs` and `consensus/src/processes/coinbase.rs`. `block-rate-devnet2.md` was still a template (RUN_A, RUN_B empty) at 21:50 UK; nothing here depends on it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Method
|
||||
|
||||
| Question | What was run | Where |
|
||||
|---|---|---|
|
||||
| Task 1, utility beyond gas | Arithmetic: the measured eleven-card table turned into a cost per v1 shard and per billion cycles (alone, beside the miner, rented), against the published prices; five demand curves on a low/base/high grid at three IGN prices | `utility.py`, output `utility_out.md` |
|
||||
| Task 2, the 80/20 and the burn under stress | `sim/economy/sim.py` copied and changed in six named places (the measured cards, the renter farm, the 25-s window and 120-s timeout, a FIFO backlog with the 600-s record window, burn per day, the new scenarios), 8 scenarios x 3 seeds, 30 days, plus two sensitivity runs; under the main checkout's run lock at nice 19 on the M5 Max, about 18 s a run | `stress.py`, outputs `results/stress_main.md`, `stress_busy.md`, `stress_elecfarm.md` |
|
||||
| Task 3, ten years without a treasury | Arithmetic: `sim/economy/security_budget.py` extended with the lane's fee grid, the pool line, the sustained hash at the measured card economics and the rented 34% weight attack at USD 281 per GH/s-day | `security_budget_10y.py` |
|
||||
| Task 4, the dev fee | Arithmetic from `miner-dev-fee.md` and the bench-log measurement | `devfee.py` |
|
||||
| Task 5, signalling | Arithmetic on the three thresholds; lane 3's `signalling.py` covers the 95-percent rule and is cross-referenced, not re-run | `signal_game.py` |
|
||||
| Task 6, the other chains | Reading: `vendor/rusty-kaspa` for Kaspa; everything else named and marked approximate where no clone exists | this file, section 5.6 |
|
||||
|
||||
No node harness was started and no measurement was taken: the fleet, the PCs and the devnet were on the class v4 rehearsal and the Devnet 2 block-rate runs. Every hardware number is the fleet's from 6 October (`prover-tiers-real-cards.md`) or the bench-log's.
|
||||
|
||||
---
|
||||
|
||||
## 2. Evidence
|
||||
|
||||
### 2.1 Measured inputs
|
||||
|
||||
| Input | Value | Source |
|
||||
|---|---|---|
|
||||
| v1 shard | 4,717,439 cycles; the adopted `S_p` is 30,000 pgas = 30 M cycles, so the fixture is 16% of a full shard | `prover-tiers-real-cards.md`; spec 5.11 |
|
||||
| Shard alone, compressed, patched server 2^26 | 3060 14.4 s, 3080 7.1, 3090 14.9, 4060 Ti 16 GB 11.6, 4060 Ti 8 GB 9.6, 4060 18.4, 4070 12.1, 4090 6.3, 5070 4.8, 5090 6.3, A5000 8.3 | `prover-tiers-real-cards.md` table |
|
||||
| Shard beside the running miner (compressed) | 3060 37.5 s, 3080 25.6, 3090 19.9, 4060 Ti 16 GB 34.6, 4070 27.3, 4090 26.1, 5070 37.2, 5090 10.7, A5000 34.6; the 8 GB cards core-only 22.1 to 26.3 | same |
|
||||
| Hash and watts mining (rented boxes) | 3060 23.78 MH/s at 103.7 W ... 5090 98.48 at 258.2 (the table) | same; the 4060's 0.0 W reading replaced by its 115 W rating, approximate |
|
||||
| The miner's loss while its card proves | 5090: 124.72 to 119.74 MH/s, 4.0%, on empty shards; 8 GB cards 17.1 to 16.0 MH/s, about 6%, on v1 shards | bench-log, proving v1 step 1; `prover-tiers-real-cards.md` 8 GB row |
|
||||
| Watts mining and proving at once | 5090: 328.6 W max against 258 W mining (about 70 W more) | bench-log proving v1 step 1; the fleet row |
|
||||
| Rental price of hash | USD 0.0117 per MH/s-hour (1,748 MH/s for USD 20.44 an hour, 38 pods); USD 11.7 per GH/s-hour, USD 281 per GH/s-day; the market gave 0 of 20 pods asked at the TH/s scale | bench-log line 2582 |
|
||||
| Aggregation per block on a mining 5090 | 9.6 to 9.7 s (2.1 s with the card to itself) | bench-log line 2349 |
|
||||
| Dev fee on a test network | 9 fee blocks in 785 (1.15%; the template rule is exact at 1 in 100) | bench-log line 1226 |
|
||||
| Proof record sizes | 274 bytes per shard record, 586 per segment record, 1,272,897 bytes per compressed proof | spec 7.7, 7.8; bench-log proving v1 |
|
||||
|
||||
### 2.2 Published prices (all approximate or secondary; none cloned)
|
||||
|
||||
| Supplier | USD per billion cycles | Label |
|
||||
|---|---|---|
|
||||
| Boundless (RISC Zero), Base | about 0.21 median lock price, trailing day 4 Oct 2026 | `developer-adoption.md` 2b, secondary summary; approximate |
|
||||
| Succinct Prover Network | 0.046 base plus up to 0.46 per billion PGU in the quickstart's EXAMPLE request at USD 0.23 per PROVE | `developer-adoption.md` 2b; example parameters, not a market price |
|
||||
| RISC Zero Bonsai | never published a per-cycle list price; paid proving moved to Boundless in 2025 | not cloned, approximate |
|
||||
| Ethereum L1 block at the ethproofs cluster cost, Sep 2026 | sub-half-cent a block; at 0.2 to 1.3 B cycles a block (14 to 44 SP1 cycles per gas, measured on Igneum) about 0.004 to 0.025 | `frontier.md` 2.6 (secondary); `base-fee-floor.md` |
|
||||
| A Taiko-class rollup per batch | taiko-mono not cloned; Taiko Alethia proves batches through its own prover market with SGX and ZK tiers (SP1 and RISC0 accepted); the ZK proof's cost per batch is of the order of the ethproofs figure times the batch's cycles: cents to tens of cents | approximate |
|
||||
|
||||
### 2.3 What the earlier models said that this lane re-tests
|
||||
|
||||
| Claim | Source | What changed tonight |
|
||||
|---|---|---|
|
||||
| Hybrid loses the whole hash for the proof's duration plus a 5-s swap | `sim/economy/sim.py` TPROVE + 2 x swap | Measured: the miner loses 4 to 6% while the card proves; the lottery wins the card's arbitration and the proof is 3 to 4x slower instead |
|
||||
| A 3060 proves a shard in 20 s (target) | ledger P1 | Measured: 14.4 s alone, 37.5 s beside the miner, on the 4.7 M-cycle fixture; a full 30 M-cycle shard is unmeasured on it (linear scaling would say 92 s alone, approximate) |
|
||||
| 10-s window, 300-s claim timeout | spec 7.2 as designed | 25 s and 120 s decided 6 Oct 2026 (ledger P9) |
|
||||
| The farm pays electricity at USD 0.05 | `sim/economy/sim.py` | The farm is a renter at the measured USD 0.0117 per MH/s-hour |
|
||||
|
||||
---
|
||||
|
||||
## 3. Model
|
||||
|
||||
### 3.1 The supply side of proving
|
||||
|
||||
For a card with hash `h` (MH/s), network hash `N` (MH/s), shard time `t` (s), watts `w` and price `P` (USD per IGN):
|
||||
|
||||
```
|
||||
cost_alone = w t / 3.6e6 x 0.10 electricity
|
||||
+ (h / N) x 0.8 x 31.688 x t x P the subsidy the card forgoes while it proves
|
||||
cost_beside = 70 t / 3.6e6 x 0.10 + (h / N) x 0.8 x 31.688 x t x 0.04 x P (4% measured on the 5090, approximate elsewhere)
|
||||
cost_rented = h x 0.0117 / 3600 x t the renter's cost; no subsidy, no electricity
|
||||
per billion cycles: x 1e9 / 4,717,439
|
||||
```
|
||||
|
||||
### 3.2 Demand curves at the adopted floors (spec 5.11; design 6 for jobs)
|
||||
|
||||
```
|
||||
transfer = 21,000 x 100 gwei + 300 x 10,000 gwei = 0.0051 IGN, burned; tip 21,000 x 1 gwei, 80% miners+provers, 20% burned (no registered frame)
|
||||
batch post 100 KB = (21,000 + 16 x 100,000) x 100 gwei + 300 x 10,000 gwei = 0.1651 IGN, burned
|
||||
job of C cycles = C / 1000 x 10,000 gwei x 1.5 = 15 IGN per billion cycles; 90% provers, 10% burned once IGN-settled
|
||||
payments cap = B_p / 300 = 400 transfers a block = 34.6 M a day; EIP-1559 target half of that, 17.3 M a day
|
||||
records = 274 x shards + 586 / 8 bytes a block in the coinbase; 1,272,897 bytes per proof on p2p
|
||||
```
|
||||
|
||||
### 3.3 Sustainability
|
||||
|
||||
```
|
||||
sustained hash (GH/s) = miners' USD per day / (electricity + capital per GH/s-day)
|
||||
electricity = 258.2 W / 98.48 MH/s x 24 / 1000 x USD 0.10 = USD 6.29 per GH/s-day (measured card, the brief's price)
|
||||
capital = USD 2,000 / 98.48 MH/s / 1,095.75 days = USD 18.53 per GH/s-day (approximate)
|
||||
total USD 24.8 per GH/s-day; the rental price is USD 281, 11.3x
|
||||
34% weight attack = rent 1.04 N for 20 days (lane 3's rule, spec 3 headline) = 1.04 x N x 281 x 20; the attacker earns 51% of the producer subsidy meanwhile
|
||||
```
|
||||
|
||||
### 3.4 The stress simulator
|
||||
|
||||
`sim/economy/sim.py` with: eleven card classes (`HASH`, `PMINE`, `TPROVE` alone, `TBESIDE`, `CANHYB` from the measured table; `MIX` an approximate installed-base shape), hybrid capacity `cards x T / TBESIDE` and hybrid hash `1 - 0.04 x duty`, operator 0 a renter (`cost = cards x MH/s x 0.0117 x hours`), window 25 s, timeout 120 s, a FIFO of open shards with a 600-s expiry (expired credit stranded), burn per day = 10% of IGN-settled external jobs plus `blocks x content_shards x 0.51 IGN` (paid content 0.03 shards a block at launch traffic), one proving shard a block (measured on the devnet). The thresholds T1 to T5 are the 4 October definitions (hash under 50% of the pre-event mean for an hour; backlog over 600 s; a growing backlog; a day under 90% within 60 s; a 10-point proving-share swing).
|
||||
|
||||
---
|
||||
|
||||
## 4. Results, task by task
|
||||
|
||||
### 4.1 Task 1: what IGN is for beyond gas
|
||||
|
||||
#### (a) Proving as a sellable service
|
||||
|
||||
| Card | Alone, 1 GH/s | Alone, 100 GH/s | Alone, 1 TH/s | Beside its miner, 100 GH/s | Rented (no subsidy) | Electricity only |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 3060 12 GB | 36.81 | 0.377 | 0.046 | 0.054 | 0.236 | 0.0088 |
|
||||
| 4070 12 GB | 32.50 | 0.331 | 0.039 | 0.041 | 0.208 | 0.0065 |
|
||||
| 4060 Ti 16 GB | 21.92 | 0.224 | 0.027 | 0.040 | 0.140 | 0.0049 |
|
||||
| 4090 24 GB | 35.38 | 0.361 | 0.042 | 0.069 | 0.227 | 0.0068 |
|
||||
| 5090 32 GB | 66.69 | 0.676 | 0.076 | 0.050 | 0.427 | 0.0096 |
|
||||
| Boundless median (approximate) | 0.21 | 0.21 | 0.21 | 0.21 | 0.21 | |
|
||||
| ethproofs L1 cluster (approximate) | 0.004 to 0.025 | | | | | |
|
||||
|
||||
USD per billion cycles at USD 0.02 per IGN (`utility_out.md` 1.3; the IGN price moves only the opportunity term).
|
||||
|
||||
What it says. Electricity is under a cent per billion cycles on every card; "marginal cost close to power" (ledger C10's wording) is true of the electricity and false of the price, because the price a prover must charge is the subsidy it forgoes, and that scales as 1 / network hash. At today's devnet scale (1.16 GH/s) a prover that stops mining to prove must charge 100 to 300x Boundless's median. At 100 GH/s a card proving alone is at 1 to 3x Boundless; a hybrid card beside its miner is at 0.2 to 0.4x (USD 0.04 to 0.08), which is the only row where Igneum undercuts the market, and it rests on the 4% figure measured on one card. The renter's row, USD 0.13 to 0.43, is the floor below which no rented prover ever sells. The floor-priced job (15 IGN per billion cycles) is USD 0.075, 0.30 and 1.50 at the three prices: a third of Boundless at 0.005, 1.4x at 0.02, 7x at 0.10. The floor is denominated in IGN and the market in dollars, and the floor moves by a two-week 60% vote (spec 5.11): it cannot follow a price. Frontier 3.11 (rank 15) already shows the market is three to four orders under year-1 emission; this lane adds that at the adopted floor Igneum overprices the market at any IGN price above about USD 0.014.
|
||||
|
||||
| Tier | Consequence |
|
||||
|---|---|
|
||||
| Home 8 GB | proves alone only (compressed does not fit beside the miner); its price is the alone row: competitive only above about 300 GH/s of network hash |
|
||||
| Home 12 GB | mines and proves on headless Linux (37.5 s beside on the 3060, 27.3 s on the 4070); competitive beside its miner at 100 GH/s; a full 30 M-cycle shard beside the miner is unmeasured (about 3 min by linear scaling, approximate, outside the 120-s claim timeout) |
|
||||
| Home 16 GB | the cheapest beside-row (USD 0.040 per billion at 100 GH/s) |
|
||||
| Home 24 or 32 GB | the 5090 is the cheapest prover per billion beside its miner above 100 GH/s and the dearest alone (its subsidy is the largest) |
|
||||
| Rig | eight 4090s beside their miners: USD 0.07 per billion at 100 GH/s; 8 x 26.1 s per shard, so a rig delivers a 30 M-cycle shard in about 21 s with all eight on one shard (approximate; SP1 proves one shard per server) |
|
||||
| Pool user | nothing: the pool's provers carry the proofs |
|
||||
| Prover | its quote is a function of network hash it does not control; publish the price as `h/N x subsidy x t`, never as a number |
|
||||
| Holder | job demand buys IGN only after the proof bridge (phase two); at launch customers pay on their own chain, so (a) is zero IGN demand at launch |
|
||||
| Rollup customer | the customer brief should carry the band above and the condition (network hash) rather than any price |
|
||||
|
||||
#### (b) Rollup settlement, (c) bridges, (d) payments, (e) storage
|
||||
|
||||
Dollars per day to miners and provers (`utility_out.md` 3.3) and burn (3.4), base scenario, USD 0.02 per IGN:
|
||||
|
||||
| Period | External jobs to provers, USD (own chain) | Rollups settling here, IGN to provers | Bridges, IGN to provers | Payment tips, IGN | IGN flows in USD | Burn, IGN | Burn, % of daily emission |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| launch | 450 | 19,440 (1 rollup) | 3,038 (1 bridge) | 1.68 (100 k transfers) | 450 | 5,748 | 0.21% |
|
||||
| year 2 | 1,800 | 58,320 (3) | 9,112 (3) | 16.8 (1 M) | 1,349 | 23,316 | 0.85% |
|
||||
| year 5 | 9,000 | 194,400 (10) | 15,188 (5) | 168 (10 M) | 4,195 | 126,716 | 4.6% |
|
||||
| year 5 high | 90,000 | 972,000 (50) | 30,375 (10) | 290 (17.3 M, the target) | 20,058 | 711,481 | 26% |
|
||||
|
||||
Low scenarios are a tenth to a fifth of these; the full grid at the three prices is in `utility_out.md`. Reading each curve:
|
||||
|
||||
- **Rollups** are the only line that pays provers in IGN at scale: one rollup posting a batch a minute with a 1 B-cycle proof job pays 19,440 IGN a day at the floor, 3.6% of the daily pool. Ten of them in year 5 pay 194,400 IGN a day, 36% of the pool before the second halving and 142% of the pool after it. The condition is the floor price staying under the market's (above). The burn it causes: 10% of the job plus the batch's base fee, 2,398 IGN a day per rollup.
|
||||
- **Bridges** at 225 updates a day pay 3,038 IGN a day each; a tenth of a rollup. No bridge is official (spec 7.3), so the count is anyone's.
|
||||
- **Payments** cost USD 0.000026 to 0.00051 a transfer (the three prices). A transfer undercuts a 1 bps rail on any payment above USD 0.26 to 5.10 and a 10 bps rail above USD 0.03 to 0.51; a USD 100 payment pays 0.003 to 0.05 bps. The fee is flat in IGN, so payments give the coin burn and almost no income: 10 M transfers a day burn 51,042 IGN (1.9% of emission) and tip 168 IGN at the 1 gwei default. The proving dimension caps the chain at 34.6 M transfers a day and the fee leaves the floor above 17.3 M; above that the burn is set by willingness to pay and no model here knows it, so the year-5 high row is clamped at the target and says so.
|
||||
- **Storage.** Records are 347 bytes a block at one shard (1,025 at 3.5): 11 to 32 GB a year, USD 0.17 to 0.50 of disk per node per year (approximate HDD price), paid by whoever runs a node and by nobody else. Proof bytes (1.27 MB each) never enter a block; a node keeps the 600-block pool (about 3 GB at four shards a block) and a light client one proof. An archive of every proof would be 80 to 180 TB a year (USD 1,200 to 2,700 of HDD, approximate): a service someone sells, not a protocol cost. Frontier 3.16 (rank 9) is the research-dataset version of the same bytes.
|
||||
|
||||
The honest total. In the base scenario all five uses together put USD 450 a day to miners and provers at launch and USD 4,200 in year 5 at 0.02, against USD 54,800 of daily emission in year 1 and 13,700 in year 5. Fees are 1.6% of security spend in year 1 and 48% in year 5 (`security_budget_10y_out.md`, base at 0.02), and most of the year-5 share is the external USD line, which is in dollars and does not move with the coin. Burn is 0.2% to 4.6% of daily emission in base scenarios. The Economics section's "part of every payment on Igneum is burned" is true and small: with the ramp's 37 M never minted, burn under 1% of emission a day leaves the cap's approach unchanged to the second decimal for years.
|
||||
|
||||
### 4.2 Task 2: the 80/20 split and the burn under stress
|
||||
|
||||
`results/stress_main.md`: 8 scenarios x 3 seeds, 30 days, the eleven measured cards, the farm (20% of hash, 925 to 1,016 5090s) a renter at USD 0.0117 per MH/s-hour, window 25 s, timeout 120 s, one proving shard a block, USD 0.012 at t = 0. The model's network is about 700 GH/s of potential hash (15,000 cards), so every number below is at that scale; the renter's rent against the subsidy is the N_eq of lane 3 (`cost_results.md` section 3): 94 GH/s at USD 0.012.
|
||||
|
||||
| Metric | a: baseline | p10: price x10 day 7 | pd10: price /10 day 7 | c: no external | x100: external x100 | cartel: top 10% of weight never proves | refuse: nobody proves days 10 to 20 | halving: 15.844 IGN a block |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| Hash min / pre-event (seed min) | 0.99 | 0.99 | 0.55 (0.49) | 0.99 | 0.91 | 0.99 | 0.99 | 0.98 |
|
||||
| Hash day 30 / pre-event | 1.00 | 1.25 | 0.74 | 1.00 | 0.97 | 1.01 | 0.99 | 1.00 |
|
||||
| T1 hours under 50% | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Cards off, day 30 | 9% | 0% | 31% | 9% | 8% | 0% | 9% | 9% |
|
||||
| Cards proving / hybrid, day 30 | 6% / 58% | 6% / 66% | 14% / 44% | 6% / 48% | 10% / 55% | 6% / 57% | 6% / 76% | 4% / 58% |
|
||||
| Backlog max, shards; T2 age max, s | 0; 0 | 0; 0 | 0; 0 | 0; 0 | 0; 0 | 0; 0 | 766; 565 (capped by the 600-s expiry) | 0; 0 |
|
||||
| Blocks within 60 s, mean / T4 worst day | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 | 0.67 / 0.00 | 1.00 / 1.00 |
|
||||
| T5 proving-share swing, points | 1.6 | 0.1 | 2.0 | 0.6 | 2.9 | 2.2 | 0.0 | 4.4 |
|
||||
| Burn, IGN a day (of which external) | 19,928 (18,606) | 6,758 (5,437) | 146,737 (145,415) | 1,322 (0) | 1,576,947 (1,575,623) | 16,954 (15,632) | 13,366 (12,044) | 17,247 (15,926) |
|
||||
| Burn, USD a day at the run's mean price | 214 | 578 | 506 | 15 | 15,535 | 218 | 148 | 224 |
|
||||
| Share of the 20% pool paid / stranded | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 1.00 / 0 | 0.67 / 0.33 (182,387 IGN a day averaged; 547,570 a day during the refusal) | 1.00 / 0 |
|
||||
| Renter farm cards on at day 30; margin over rent | 0; -77% | 984; +114% | 0; -77% | 0; -76% | 54 (0 to 162); -6% | 984 (forced on); -79% | 0; -77% | 0; -88% |
|
||||
| Flags tripped (of 3 seeds) | none | none | none (T1 min 0.49 in one seed, under an hour) | none | none | none | T4 3/3 | none |
|
||||
|
||||
Per card class, USD per card-day (every mode, off included) and the share of shards proven, baseline: 3060 1.30 (0%), 3080 3.09 (5%), 3090 2.69 (11%), 4060 Ti 16 GB 1.63 (4%), 4060 Ti 8 GB 2.03 (16%), 4060 0.85 (1%), 4070 1.96 (12%), 4090 3.86 (11%), 5070 3.36 (12%), 5090 3.95 (24%), A5000 3.30 (4%). The full tables are in `results/stress_main.md`.
|
||||
|
||||
What holds and what breaks:
|
||||
|
||||
1. **The 80/20 holds under every price and demand shock; the price is what moves hash.** T1 never trips. The /10 price shock is the closest: hash troughs at 55% of pre-event (49% in one seed for under an hour), 31% of cards go off (the 3060 and 4060 classes at median electricity and above), and the chain is at the T1 line with no backlog and every block proven inside 60 s. A x10 shock brings the renter farm on (margin +114%) and hash ends 25% up: rented hash arrives exactly when the subsidy per GH/s-day clears USD 281, which is lane 3's N_eq. The first halving at a flat price changes nothing at this price level (the same 9% off), because the cards that remain are above break-even at 0.013; a halving is a /2 price shock and the /10 row says where /2 would land between the two.
|
||||
2. **External demand x100 is the burn story and the renter story.** USD 200,000 a day of jobs at 10% burn is 1.58 M IGN a day burned, 58% of daily emission, at the run's price of about USD 0.01; and it brings 0 to 162 rented 5090s on at a -6% margin. Hash troughs at 91%: cards leave the lottery for jobs (the 4 October finding, scenario b), and the renter's cards arriving for jobs do not hash. The burn in that row is a transfer from customers to holders of 15,500 dollars a day; it is the only row where burn is material, and it needs IGN settlement, which is phase two.
|
||||
3. **A cartel of the top 10% of weight that never proves costs the chain nothing.** In this population the top 10% of weight is one operator, the 20% farm, so the row is a 20% cartel: with 8 draws by weight the chance that every assignee is the cartel's is 0.2^8, under three in a million, so almost every shard still finds an assignee and the rest go open after 25 s; every block is proven inside 60 s, the backlog is zero, and the cartel loses USD 6.77 per card-day (forced on, as a renter, to hold its weight). Sortition with 8 draws is why: the 4 October result (scenario e, 30%) stands with the measured cards.
|
||||
4. **A refusal by every prover is the one scenario that breaks a threshold, and what breaks is the pool, not the chain.** For ten days no block is proven (T4 0 on every refusal day), execution and finality do not wait (spec 5.3, ledger P9), and the backlog never passes 600 s because the record window expires the shards: 547,570 IGN a day of pool credit is stranded in the escrow, 5.5 M IGN over the ten days, and no rule returns it. When provers come back the queue is at most 600 s deep and clears in minutes; 76% of cards end in hybrid. The refusal's whole cost is the refusers' own income plus a silent supply reduction nobody voted for (proposal 2).
|
||||
5. **The window excludes the slow hybrids.** At 25 s a 3060 beside its miner (37.5 s on the small fixture), a 4060 Ti 16 GB (34.6), a 5070 (37.2) and an A5000 (34.6) cannot land an assignment and win only open races or prove alone; the 3060 class proves 0% of shards in every scenario, the 4060 1%. The 4060 Ti 8 GB proves 16% by proving alone at 9.6 s. The 4 October proposal (window = the fleet's 90th-percentile shard time plus a swap) would set it near 38 s on this fixture; on a full 30 M-cycle shard the number is unmeasured (proposal 8).
|
||||
6. **Burn at launch traffic is USD 15 to 220 a day**, 0.05% to 0.7% of emission; the base fee part is 1,322 IGN a day at 0.03 content shards a block. Everything above that is the external 10%, which does not exist until jobs settle in IGN.
|
||||
|
||||
Sensitivities (`results/stress_busy.md`, `stress_elecfarm.md`, 2 seeds each). At 3 proving shards a block (the 4 October busy value, 3x the load) nothing changes in a, cartel or x100: backlog 0, every block inside 60 s, hash min 0.95 to 1.00; the refusal strands the same 33% of the pool with a queue of 2,265 shards at its deepest, and the renter farm comes on in x100 at 213 cards (181 to 244). With the farm on electricity at USD 0.05 instead of rent (the 4 October assumption) the baseline has 0% of cards off (the farm's 968 cards stay on) and the /10 shock takes 25% of cards off with hash troughing at 63% of pre-event and ending at 77%: the rent is what decides whether the 20% farm is on at all, and with it 20 points of hash at every price; the `renter farm margin` column of that file is undefined at zero rent and should be read as blank.
|
||||
|
||||
| Tier | Consequence of the stress runs |
|
||||
|---|---|
|
||||
| Home 8 GB | proves alone and wins open races (16% of shards on the 4060 Ti 8 GB); the first class off in a /10 shock on expensive power |
|
||||
| Home 12 GB | the 3060 proves 0% at the 25-s window; the 4070 12% (27.3 s beside, loses the window, wins open races alone); first off in a price fall |
|
||||
| Home 16 GB | 4% of shards; stays on in every row but pd10 |
|
||||
| Home 24 or 32 GB | 11 to 24% of shards; hybrid is the dominant mode (58% of all cards); the last class off |
|
||||
| Rig | as the 24 GB card per card; a rented rig is off below N_eq and on above it (p10 row) |
|
||||
| Pool user | the pool's provers' share; unchanged by any row |
|
||||
| Prover | income is 20% of emission in every row but refuse, where the refusers strand it; the x100 row is the one where jobs pay more than the pool |
|
||||
| Holder | burn is 0.05 to 0.7% of emission a day at launch; 58% in the x100 row, phase two only |
|
||||
| Rollup customer | every job delivered in every row but refuse (67%) |
|
||||
| Node operator | the backlog never passes the 600-s record window because the window expires it |
|
||||
|
||||
### 4.3 Task 3: no-treasury sustainability over ten years
|
||||
|
||||
`security_budget_10y_out.md`. The brief's formula gives a sustained hash proportional to the miners' dollars and an attack cost proportional to that hash, so the ratio is a constant: a 20-day 34% weight attack rents 1.04 N at USD 281 per GH/s-day against an honest fleet that costs USD 24.8 per GH/s-day, 11.8x the honest fleet's 20-day cost, minus the 51% of subsidy the attacker earns back. The subsidy never falls under the attack cost in ratio terms; the halvings shrink both until the absolute number is small. The honest statement is the absolute net cost by year:
|
||||
|
||||
| Year | Net cost of the 20-day veto, USD, at 0.005 | at 0.02 | at 0.10 | Sustained hash at 0.02, GH/s | Fees as % of total security spend, base at 0.02 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 2,375,000 | 9,501,000 | 47,506,000 | 1,699 | 1.6% |
|
||||
| 3 | 1,233,000 | 4,933,000 | 24,666,000 | 882 | 18.6% |
|
||||
| 5 | 617,000 | 2,467,000 | 12,334,000 | 441 | 48.3% |
|
||||
| 7 | 308,000 | 1,234,000 | 6,168,000 | 221 | 65.1% |
|
||||
| 9 | 154,000 | 617,000 | 3,085,000 | 110 | 78.9% |
|
||||
|
||||
The veto's net cost drops under USD 1 M in year 5 at 0.005, year 9 at 0.02, and not within ten years at 0.10; under USD 100 k only after year 10 at 0.005. The rental market's supply, not its price, is the other bound (0 of 20 pods at the TH/s scale on 6 October), and it is not modelled. What the fees change: in the base scenario the proving-pool and job lines reach provers, not miners, and the brief's security line is miners' hash; of the 48% fee share in year 5, under 1% reaches miners (tips at 1 gwei). The chain's security budget after year 5 is the subsidy to miners, and nothing else in the design pays for hash. Frontier 3.1 (rank 7) redirects part of the subsidy to the pool during rental spikes and would lower the miners' line further; this lane's number for it is in 5.1.
|
||||
|
||||
The audits. `funding.md` prices the first cryptanalysis at USD 80,000 to 160,000 and nothing prices the second, which a class or era change in year 3 would need. What the entity's own lines earn (every price an input):
|
||||
|
||||
| Year | Price | Dev fee, 50% of hash on Ember | Entity's provers at 5% of the pool | Second cryptanalysis (USD 160 k) as % of the dev fee |
|
||||
|---|---|---|---|---|
|
||||
| 3 | 0.005 | 10,000 | 25,000 | 1,600% |
|
||||
| 3 | 0.02 | 40,000 | 100,000 | 400% |
|
||||
| 3 | 0.10 | 200,000 | 500,000 | 80% |
|
||||
|
||||
The dev-fee row here uses 1% of the producer share (80% of emission), because the fee template moves only the `IGNA` payout and the pool is paid per record; `funding.md` section 4 took 1% of all rewards and overstates the ceiling by a quarter (48,000, 193,000 and 963,000 should read 38,520, 154,080 and 770,400). The honest options for the second audit, ranked:
|
||||
|
||||
| Rank | Option | What it pays in year 3 at 0.02 | Why this rank |
|
||||
|---|---|---|---|
|
||||
| 1 | The entity's own provers (5% of the pool and a share of jobs) | USD 100,000 a year at 5% of the pool, more with jobs | Open-market income the design already names (spec 5.5); scales with the chain, no rule, no switch; the cost is running cards |
|
||||
| 2 | The Ember dev fee | USD 40,000 a year at 50% of hash on Ember | Exists and is measured; falls with every halving and with every miner who flips the switch; alone it funds a review every four years at 0.02 |
|
||||
| 3 | A user-paid review market: customers (rollups) co-fund the audit that protects their settlement, as a condition of their integration | unknown; a Taiko-class customer's whole annual proving spend is of the order of USD 100 k (frontier 3.11) | Honest and voluntary; the customer has the motive; it depends on having a customer |
|
||||
| 4 | Founders' mined coins (the litepaper's own answer for grants) | depends on hash share | Visible addresses; finite; the ledger's E2 and E8 live here |
|
||||
| 5 | A burn-funded bounty or review escrow | the base-fee burn at launch traffic is 51 to 20,578 IGN a day: USD 1 to 412 at 0.02 | Frontier 3.6 (rank 11, "watch") and its Monero attack: a burn redirect is a payee by rule, which is the switch spec 5.5 removed. The protocol cannot have it because it has no treasury, and that is the contradiction stated plainly: the no-treasury rule means the SECOND audit is paid by whoever earns in the open or it is not paid, and `funding.md` should say so in a row of its own |
|
||||
|
||||
### 4.4 Task 4: the dev fee
|
||||
|
||||
What it is (`miner-dev-fee.md`): one block template in 100, chosen by an exact counter (templates 99, 199, ...), is requested with the project's payout address in the coinbase extra data; the vote key and the UTXO address stay the user's, so a fee block still votes for the user and only the execution-layer payout moves. Default on; `--dev-fee 0`, the app's Settings switch or HiveOS `DEV_FEE=0` turns it off; the start line prints the state; `igneum-miner payouts` tags the dev address on the chain. Measured: 9 fee blocks in 785 on a test network, the miners' counters and both nodes agreeing (bench-log line 1226).
|
||||
|
||||
What it pays (`devfee_out.md`): 1% of the producer share of emission times the share of hash on Ember with the fee on.
|
||||
|
||||
| Year | Price | 20% keep it on | 50% | 100% | Home 4070 at 100 GH/s network, a month | Rig 8x 4090, a month |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | 0.005 | 7,704 | 19,260 | 38,520 | | |
|
||||
| 1 | 0.02 | 30,816 | 77,040 | 154,080 | USD 3.33 (6.6 blocks) | USD 55.74 (110 blocks) |
|
||||
| 1 | 0.10 | 154,080 | 385,200 | 770,400 | | |
|
||||
| 5 | 0.02 | 8,000 | 20,000 | 40,000 | | |
|
||||
|
||||
What share would turn it off. Precedent (approximate, from memory): T-Rex 1%, lolMiner 0.7 to 1.5%, PhoenixMiner 0.65%, TeamRedMiner 0.75 to 2.5% and NBMiner 1 to 2% were not switchable, and together they held the large majority of Ethereum's GPU hash over the fee-free ethminer because they were faster; NiceHash is a marketplace that takes about 2% of the buyer's payment, not a dev fee; nobody measured an opt-out share because none offered one. Igneum's switch is one flag and the miner is open source, so the rational solo miner with any time at all turns it off; the pool operator decides for its members; the one-click app user keeps the default. A working estimate for planning: 20 to 50% of hash keeps it on, which is the devfee table's first two columns and USD 7,700 to 77,000 a year in year 1 at 0.005 to 0.02. This is a planning input, not a measurement, and the first month of the public testnet measures it from the chain (`payouts`).
|
||||
|
||||
Is "optional" honest? Ledger E18's charge is "a protocol fee with better PR". Three facts answer it. It is not in the protocol: the chain pays whatever `IGNA` address the template names, and a block with the dev address is indistinguishable in consensus from a block paying any other address. It is switchable in one flag and the chain shows who paid (`payouts`). It is default-on, and defaults are what most users run, so "optional" describes the mechanism and "default-on, switchable" describes the behaviour. The public line should be the second: "1 block in 100 pays the project unless you turn it off". Two things to add to E18: the ceiling correction above, and the fact that the fee buys the project a visible address holding 1% of mined coins, which is the E5 critic's point restated as a holder consequence.
|
||||
|
||||
| Tier | Consequence |
|
||||
|---|---|
|
||||
| Home 8 to 32 GB on Ember | 1% of blocks unless switched off; USD 2.34 to 13.13 a month at 0.02 and 100 GH/s network |
|
||||
| Rig | the same per card; a rig operator on HiveOS sets `DEV_FEE=0` once |
|
||||
| Pool user | the pool's choice: a pool on its own template software pays 0%; a pool on Ember pays 1% of its templates and passes it on or not |
|
||||
| Prover | untouched: the pool share is paid per record, never through a template |
|
||||
| Holder | one address accumulates up to 1% of producer emission; the project's incentive to keep Ember the fastest client is the fee |
|
||||
| Rollup customer | nothing |
|
||||
|
||||
### 4.5 Task 5: miner-signalled parameters
|
||||
|
||||
What genesis leaves to miners (spec 5.5, 5.9, 5.11): the base-fee floors `f_e` and `f_p`, the proving budget `B_p` (and with it `S_p`), set by a proposal at 60% of blue blocks over 1,209,600 DAA s (two weeks), the BIP 9 model. Upgrades (new code) need 90% (spec 5.7, window open, O-5.3). The P2 rule for a PoW class change needs 95% of blue blocks over a one-day window ending at each epoch's seed block, monotone, with a floor height as the backstop (`counter-asic-3-node.md` section 6: `CLASS_SIGNAL_THRESHOLD_BPS` 9,500, window 86,400 DAA, the fast-time gate green on three cases and its failed case). The documents disagree about the number: spec 5.7, CLAUDE.md's design paragraph and the litepaper's Governance section say 90% for upgrades; the P2 design and the Horizon preamble say 95% for class changes; the litepaper's Mining section says "a 90% miner signal turns one on". One sentence should carry all three (60 parameter, 90 upgrade, 95 class with a floor) or the three should become two.
|
||||
|
||||
The game (`signal_game_out.md`; lane 3's `signalling_results.md` for the 95% rule):
|
||||
|
||||
| Rule | Who can block | A 30% pool | Renter's cost to force at 100 GH/s | What ends a block |
|
||||
|---|---|---|---|---|
|
||||
| 60% over 14 days | over 40% of blue blocks | cannot block alone; needs 11 more points | USD 590,000 (1.5 N for 14 days) | the proposal fails; re-register |
|
||||
| 90% over 14 days | over 10% | blocks it | USD 3.5 M (9 N) | the proposal fails; re-register |
|
||||
| 95% over 1 day, floor | over 5% | blocks it | USD 534,000 (19 N for a day) | the floor height |
|
||||
|
||||
A 6% holdout costs USD 18 a day at 1 GH/s and USD 1,800 at 100 GH/s on top of the subsidy it earns like anyone, so near zero (lane 3 section 2); it buys delay to the floor and nothing else. A 30% pool holds a permanent veto over upgrades at 90% and over class changes until the floor at 95%; the devnet's top three vote keys held 34.5% of blocks on 4 October (litepaper, Governance). Signal then defect is bounded by what is signalled: a PoW class defector loses its own blocks (its PoW fails, `check_header_version` then the PoW check); a consensus-rule defector forks itself and whoever trusts it; an execution-parameter defector produces VALID blocks with a different state (blocks carry no state claim, design 1.1), which is a silent state fork for that node unless the parameter is in the consensus digest that the handshake refuses (G12, X18): `Params.fees` is in the digest (spec 5.11), so today it is isolated rather than split, and any future miner-signalled execution parameter must enter the digest the same day or the defector is a quiet fork.
|
||||
|
||||
What Bitcoin and Kaspa did. BIP 9: version bits, a 95% threshold of 2,016-block retarget periods, states DEFINED, STARTED, LOCKED_IN, ACTIVE, FAILED, a timeout; BIP 8 added a lock-in-on-timeout flag so a flag day ends a holdout (bips repository, bip-0009.mediawiki and bip-0008.mediawiki; not cloned, approximate). Kaspa's Crescendo (1 to 10 BPS) was a fixed DAA score, not a signal: `crescendo_activation: ForkActivation::new(110_165_000)` for mainnet and `88_657_000` for testnet, with `ForkActivation::is_active(daa)` as `current_daa_score >= self.0` (`vendor/rusty-kaspa/consensus/core/src/config/params.rs` lines 28 to 60, 648, 704, main checkout), and the coinbase keeps the activation score for ever to compute the subsidy month across it (`consensus/src/processes/coinbase.rs` lines 40 to 43, 238 to 253); `docs/crescendo-guide.md` tells miners to upgrade before the activation. The P2 rule is BIP 8 in shape: a signal path plus a flag day. Igneum's 6 October incident (DAA 198,000 crossed by a half-updated fleet) is the flag-day hazard, and P2's floor keeps it.
|
||||
|
||||
What SHOULD be miner-signalled and is not, with the risk of each:
|
||||
|
||||
| Parameter | Today | Should be | Risk if signalled | Risk if not |
|
||||
|---|---|---|---|---|
|
||||
| The block rate step (1 to 4 to 10 BPS) | a planned fork with its own test campaign, "as Kaspa's Crescendo" (spec 2.1) | a 90% upgrade signal with a floor, like P2: it is a consensus change crossed by a whole fleet | a 10% pool vetoes the step; a renter forces it a day early for USD 5.3 M at 1 TH/s | a fixed height on a half-updated fleet: the 229-block reorg of 6 October at mainnet scale |
|
||||
| The dataset growth step | automatic, genesis schedule (spec 1, 2 GiB doubling at years 4, 12, 28) | NOT signalled, by design: it is an anti-ASIC escalator and a chip-holding cartel would vote growth down. Allow a 60% signal to ACCELERATE only (monotone), never to delay | a 40% holdout blocks acceleration: no worse than today | none: the schedule runs |
|
||||
| The 80/20 lottery/proving split | fixed (spec 2.5) | a 60% parameter inside a hard band [10%, 30%] | 80% of the voters are the lottery; without the band they vote the pool to 0 and the provers go; with the band the worst case is 10% | the simulator says 20% is not load-bearing at launch traffic and 30% helps at 100 shards a block (economy-2026-10-04 5.3); fixed means a 90% upgrade to move it |
|
||||
| The base-fee floors and `B_p` | 60% over 14 days (spec 5.11) | a bounded per-block dial, Ethereum's gas-limit mechanism (frontier 3.5, rank 8): the dollar market moves faster than two weeks (4.1) | a 51% majority walks the dial to the bound in days; the bound and a cost curve are the defence | the job price is pinned in IGN while the market is in dollars; at 0.10 the floor is 7x Boundless and a two-week vote cannot follow it |
|
||||
| The job premium 1.5 and the external claim timeout 120 s (O-5.6) | design 6 constants | the same bounded dial | as above | a constant calibrated once on the phase 4 devnet |
|
||||
| The exclusive window 25 s | a consensus constant (P9) | a function of the fleet's measured shard-time distribution, published per era (economy-2026-10-04 proposal 1; frontier I3) | none: it reads a measurement | a 12 GB fleet whose shard time drifts past the window loses every assignment to the open race (the 4 October finding at 10 s) |
|
||||
|
||||
### 4.6 Task 6: what Kaspa, Monero, Ethereum and the zk rollups did and got wrong
|
||||
|
||||
| Area | Chain | What it did | Where | What went wrong, or what it costs | Igneum's rule | Avoids or repeats |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Emission | Kaspa | A pre-deflationary phase at 500 KAS a block (`pre_deflationary_phase_base_subsidy: 50000000000`, `deflationary_phase_daa_score: 15778800 - 259200`), then the chromatic schedule: 426 monthly steps, each month's subsidy the previous times 2^(-1/12), from 440 KAS a block (`SUBSIDY_BY_MONTH_TABLE[0] = 44000000000`), halving every twelve months smoothly | `vendor/rusty-kaspa/consensus/core/src/config/params.rs` 631 to 638, 687 to 694; `consensus/src/processes/coinbase.rs` 22 to 25, 222 to 253, 280 | Steep and smooth: no halving-day cliff, but the subsidy fell 50% a year and the chain leaned on price appreciation it could not promise; the table is divided by BPS at Crescendo so the per-second rate is unchanged | 1 B a year halving every two years, in DAA seconds; a 30-day ramp; no tail (spec 2.5, 5.10) | Avoids the yearly rate (slower), repeats the cliff (a step, not a glide); E6 concedes it |
|
||||
| Emission | Monero | A tail emission of 0.6 XMR a block for ever after the main curve | monero repository `src/cryptonote_basic/cryptonote_basic_impl.cpp`, `get_block_reward` (not cloned, approximate) | Security paid for ever at about 0.9% a year inflation falling toward zero; the cost is a soft supply cap critics name | No tail; a review trigger that puts a tail to a 90% vote if proving revenue is under a fifth of the subsidy after year 5 (spec 5.10.3) | Repeats Bitcoin's bet, keeps Monero's door ajar by vote |
|
||||
| Emission and burn | Ethereum | EIP-1559: the base fee burned, the tip to the proposer; issuance by stake since the Merge, about 0.5 to 1% a year gross, net near zero when burn is high | ethereum/EIPs `EIPS/eip-1559.md`; ethereum/execution-specs `src/ethereum/london/fork.py` (`calculate_base_fee_per_gas`); not cloned, approximate | The burn removes the proposer's incentive to stuff blocks, at the cost that usage pays security nothing; proposers' income moved to tips and MEV | Both base fees burned, tip 80/20 to miners-provers and apps; the same trade-off, stated (security-budget.md section 5) | Repeats on purpose (E3 is the reason); the EIP-1559 step is copied (`next_base_fee`, denominator 8) |
|
||||
| Fee market | Ethereum | A base fee that cannot fall below 7 wei in practice and has no floor; the gas limit voted per block by proposers within 1/1,024 | execution-specs `fork.py`; geth `core/block_validator.go` VerifyGaslimit; approximate | A near-zero base fee when idle makes spam cheap; the gas-limit vote is the one continuous miner dial that worked for a decade | A floor per dimension (spec 5.11) calibrated for spam; `B_p` and the floors by a two-week 60% vote | Avoids the idle-spam gap; does not take the per-block dial (frontier 3.5 asks for it) |
|
||||
| Proving market | Aleo | Proof-of-succinct-work: provers compete on proofs for coinbase rewards; the fastest prover (GPUs, then FPGAs and ASICs) took the reward share | AleoNet/snarkOS and snarkVM (not cloned, approximate; CLAUDE.md "the Aleo lesson", ledger C9) | The proving reward centralised to the fastest hardware; small provers earned nothing | The lottery and the proving are separate; shards by sortition on 30-day weight, 8 assignees, 25 s, then open (spec 7.2) | Avoids the race for assigned shards; the open race after the window is where fast cards win beyond their weight (economy-2026-10-04 3.1 item 5) |
|
||||
| Proving market | Boundless (RISC Zero) | A reverse auction per request; provers post ZKC collateral; PoVW pays ZKC per cycle proven | docs.boundless.network/zkc/mining/overview and provers/performance-optimization (read, not cloned) | A token gate on supply and a stake that scales with work; the median price USD 0.21 per billion (approximate) | No bond for shards; a coin bond only on external jobs (O-5.6); frontier 3.2 (rank 2) replaces even that with work-stake | Avoids the token gate for internal proving; repeats a bond for jobs |
|
||||
| Proving market | Succinct | A real-time auction settled in PROVE; provers stake PROVE to bid | docs.succinct.xyz/docs/provers (read, not cloned; ledger C10) | The same gate; example prices, no public market price | As above; prices in dollars settled in the token (spec 5.4) | Avoids the gate; repeats "settled in our token" once IGN settlement starts |
|
||||
| Governance | Monero | Scheduled hard forks (six-monthly, now 9 to 12 monthly), decided by the core team and the community off-chain | getmonero.org and the monero repository's release history (approximate) | Works because the community trusts a small team; the schedule itself is a central clock | No scheduled human releases; automatic escalators at genesis; 90% (or 95%) miner signalling for anything else (spec 5.7) | Avoids the clock; the price is that pools hold the vote (G8) |
|
||||
| Governance | Kaspa | KIPs discussed off-chain, activated at fixed DAA scores; Crescendo at 110,165,000 after a testnet campaign | `params.rs` 648; `docs/crescendo-guide.md` | A flag day; a node not upgraded forks off; it worked because the community upgraded in time | P2: a signal plus a floor height; Devnet 2 as the staging chain for every cut (CLAUDE.md 6 Oct rules) | Avoids the bare flag day, keeps it as the floor |
|
||||
| Governance | Ethereum | All Core Devs calls decide; clients ship; activation by timestamp; no on-chain vote | ethereum/pm repository (approximate) | Works by rough consensus among client teams; a single client bug is a chain-wide event (the 2016 Shanghai attacks, the 2020 Geth split, approximate) | One client today; a second independent client is the first priority after launch (litepaper, Governance) | Repeats the single-client risk until the second client exists |
|
||||
| Rollups | Taiko and the zk rollups | Pay their own prover networks per batch; based sequencing; multi-proof tiers | taiko-mono (approximate) | Proving cost is a line item that falls 3 to 30x a year (frontier 2.6); settlement and proving are bought from two suppliers | Settlement and proving from the same miners in one flow (litepaper, Building) | New; the price condition is 4.1 (a) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Ranked proposals
|
||||
|
||||
| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Decouple the job price from `f_p`: a job's reserve is the measured proving electricity per pgas (USD 4.4e-9 at 0.15 per kWh, base-fee-floor.md 3) converted at a published settlement rate, and the requester bids above it; the 1.5 premium becomes a bid, not a floor | At the adopted floor a billion-cycle job is 15 IGN = USD 0.075 / 0.30 / 1.50 at the three prices against Boundless's 0.21 (4.1 a); a two-week 60% vote cannot follow a dollar market | `utility.py` section 2 | 16: the reserve rule in `Prover.request` (6), the rate oracle as the review-trigger's published reading (spec 5.10.3 already defines it) (4), spec 5.4 and design 6 text (6) | Prover: sells at the market, not at a vote; Rollup customer: a quote it can compare; Holder: job demand for IGN survives a price rise; Miner: nothing; Pool user: nothing | A simulated job book at the three prices clears within 20% of Boundless's median at every price |
|
||||
| 2 | Define the stranded pool: an unproven shard's credit rolls forward into the next proven segment's pool instead of sitting in the escrow for ever | The spec is silent on credit nobody claims; a 10-day refusal strands 5.5 M IGN (4.2); the devnet already burns the coinbase 20% output (litepaper, Economics) | `stress.py` refuse scenario, `stranded_share` | 8: the roll-forward in `split_pool_credit` (4), spec 5.3 and 7.8 item 7 text (2), a unit test with a 10-segment gap (2) | Prover: a refusal costs the refusers and pays the returners; Holder: no silent burn; Miner: nothing | On the fast-time harness, 100 unproven segments then 10 proven: the escrow returns to zero within the 10 |
|
||||
| 3 | Publish the prover's price as a formula, never a number: `price per billion = (h / N) x 0.8 x 31.688 x t x P x 212 / cycles`, with N the live network hash | The same card is 100 to 300x Boundless at 1 GH/s and 0.2 to 0.4x at 100 GH/s (4.1 a); the customer brief says "priced in dollars" with no condition | `utility.py` 1.3 | 3: a paragraph in the customer brief and the litepaper's Proving section, with the table | Rollup customer: no promise it cannot hold the project to; Prover: knows when to sell; everyone else: nothing | The brief and the litepaper carry the condition before any customer conversation |
|
||||
| 4 | Make the 80/20 split a 60% parameter inside a hard band [10%, 30%], and record the three signalling numbers (60 parameter, 90 upgrade, 95 class with floor) in one sentence in spec 5.7, CLAUDE.md and the litepaper | The split is not load-bearing at launch traffic and 30% buys backlog relief at 100 shards a block (economy-2026-10-04 5.3); the documents carry two upgrade thresholds (4.5) | `stress.py` `--set pool=` | 10: the band in `Params` (4), the proposal kind (3), text (3) | Prover: a floor of 10% of emission by rule; Miner: a vote on its own share, bounded; Holder: nothing | The fast-time harness: a 60% vote moves the pool to 30%; a 100% vote cannot pass 30% or go under 10% |
|
||||
| 5 | Every miner-signalled execution parameter enters the consensus digest the same release, with a CI check that fails a `Params` field marked signalled and absent from the digest | A signal-then-defect on an execution parameter is a silent state fork unless the handshake refuses the defector; `Params.fees` is in the digest, nothing guarantees the next one is (4.5) | `signal_game.py` section 3 | 6: the check in `tools/ci` (4), a test (2) | Node operator: a defector is isolated, never quietly wrong; everyone else: nothing | The check fails on a planted field and passes on the live set |
|
||||
| 6 | The block-rate steps become P2-shaped activations (signal plus floor), not fixed heights | Crescendo was a fixed DAA score (`params.rs` 648); the 6 October incident was a fixed height; spec 2.1 still says "a planned fork" (4.5) | lane 3 `cost_results.md` forced-flip row | 4: spec 2.1 text and a line in the Devnet 2 gate | Miner and rig: no flag day crossed while updating; Pool user: nothing | Spec text; the first step's rehearsal on Devnet 2 passes the same gate as the class v4 cut |
|
||||
| 7 | Correct `funding.md` section 4's dev-fee ceiling (1% of the producer share, not of all rewards) and add a row that names who pays the SECOND cryptanalysis | 48,000 / 193,000 / 963,000 overstate by a quarter (4.4); no row prices a second review (4.3) | `devfee.py`, `security_budget_10y.py` section 3 | 1 | Holder and critic: a number that matches the mechanism | The file's git history |
|
||||
| 8 | Measure the two numbers every price here rests on: the miner's hash loss while each card proves (4% is one card), and a full 30 M-cycle shard beside the miner on the 12 GB and 16 GB tiers | The hybrid row is the only one that undercuts the market and it rests on one measurement (4.1 a); the 4.7 M fixture is 16% of `S_p` (2.1) | `utility.py` `hybrid_hash_loss` | 6 on the fleet: eleven boxes, two fixtures, `tools/fleet/lib` | Home 12 and 16 GB: whether they are provers at all beside their miner; Rig: the same per card | Eleven rows with both numbers in `prover-tiers-real-cards.md` |
|
||||
|
||||
**1. Decouple the job price from `f_p`.** `f_p`'s floor exists to price spam above the electricity it imposes (base-fee-floor.md section 3: 230x the electricity at USD 0.10 per IGN). Design 6 then prices every external job at `maxPgas x f_p x 1.5`, so the same floor that is 230x electricity for spam is the job market's minimum: 15 IGN per billion cycles, which is a third of Boundless at USD 0.005 and 7x at 0.10. A rollup compares in dollars every week; a 60% vote takes two weeks and a quorum. Lane 7 (frontier 3.5, rank 8) proposes the continuous dial for the floors themselves and it would help; this proposal is narrower and independent of it: the job reserve is the electricity, published as a rate the review trigger of spec 5.10.3 already needs ("converted at the window's settlement rate and published with the reading"), and the price above the reserve is the requester's bid against the sortition's assignees. Cost 16 hours. Gate: a simulated job book clearing within 20% of Boundless at all three prices. Per tier: the prover sells at a market price; the rollup customer gets a comparable quote; the holder keeps job demand for IGN through a price rise (at 0.10 and the floor, every rollup leaves); miners, pools and home cards see nothing.
|
||||
|
||||
**2. Define the stranded pool.** Spec 5.3 pays "the first valid proof included in a block"; 7.7 item 3 refuses a record older than 600 chain blocks; 7.8 item 7 says an unproven segment's aggregator share "stays in the escrow". Nothing says what happens to the shard credit nobody claimed. In the refusal scenario (4.2) the whole 20% is stranded for ten days: 5.5 M IGN that reach nobody and that nobody decided to burn. A roll-forward (the next proven segment's pool is larger by what was stranded) makes a refusal a transfer from refusers to returners, which is the incentive the design wants, and makes the pool's total over any month equal to 20% of emission as the litepaper's table promises. 8 hours. Gate on the fast-time harness.
|
||||
|
||||
**3. The prover's price as a formula.** The whole of 4.1 (a) is one line: price per billion cycles = the subsidy the card forgoes per shard, which is `h/N`. At the devnet's 1.16 GH/s every quote is 100x the market; at 100 GH/s hybrids undercut it. The customer brief's "priced in dollars per proof" and the litepaper's "proofs at the cost of power" need the condition beside them, or the first customer conversation ends with the number. 3 hours of text.
|
||||
|
||||
**4. The 80/20 as a bounded parameter, and one sentence for the thresholds.** The 4 October lever study found the pool share not load-bearing at launch traffic and useful at 30% under heavy traffic; tonight's runs (4.2) agree. A band of 10 to 30% lets miners trade lottery for proving capacity when the traffic says so, and the band stops the lottery's 80% from voting the provers out. The same change should carry the three signalling numbers in one place; today a reader finds 90 in spec 5.7 and CLAUDE.md, 95 in P2 and the preamble, 60 in 5.5, and "a 90% miner signal turns one on" in the litepaper's Mining section about a class change the P2 rule sets at 95.
|
||||
|
||||
**5. Signalled execution parameters enter the digest, by CI.** Blocks carry transactions only. A node that signalled a fee change and runs the old rule accepts every block and computes a different state; its proof records fail everyone else's statement and everyone else's fail its own, which is loud for provers and silent for a wallet. `Params.fees` is in the digest and the handshake refuses a different digest, so today the defector is cut off. The next signalled parameter has no such guarantee until a check fails without it. 6 hours.
|
||||
|
||||
**6. Block-rate steps as signal-plus-floor.** Spec 2.1 names the steps "a planned fork with its own test campaign, as Kaspa's Crescendo". Crescendo was a fixed DAA score (`params.rs` line 648) and Igneum's own fixed height cost it a 229-block reorg on 6 October. P2 exists; the steps should use it. 4 hours of text and a gate line.
|
||||
|
||||
**7. The funding corrections.** One number and one row. 1 hour.
|
||||
|
||||
**8. Measure the two numbers.** The hybrid row is the only competitive one and it rests on the 5090's 4% and a fixture a sixth of a full shard. Six hours on the fleet, through `tools/fleet/lib`, eleven boxes.
|
||||
|
||||
Cross-references to lane 7 by name and rank: 3.1 (rank 7, the rental tax) would move 25 to 75% of a spiking block's subsidy to the pool; against 4.3's constant 11.8x ratio it doubles the renter's break-even and does not change the year the absolute cost gets small. 3.2 (rank 2, work-stake) removes the coin bond this lane's job model carries; the numbers here do not depend on the bond's form. 3.5 (rank 8, continuous dials) is the general form of proposals 1 and 4 here. 3.6 (rank 11, burn bounties) is option 5 of 4.3 and is rejected on the same ground. 3.11 (rank 15) and 3.12 to 3.14 are the market-size and verifiable-compute ceilings this lane's demand grid sits under. I7 (equivocation bounty in sortition slots) is the one treasury-less incentive in lane 7 that this lane's stranded-pool rule could fund without coins: stranded credit to the evidence carrier is a variant worth one line in the ledger, not a proposal here.
|
||||
|
||||
---
|
||||
|
||||
## 6. Open questions and what I could not run
|
||||
|
||||
- **The full-shard beside-the-miner times** on every tier (proposal 8). Linear scaling from the 4.7 M fixture says 92 s alone on a 3060 and 240 s beside the miner; if that holds, no 12 GB card meets the 120-s claim timeout beside its miner and the 25-s window is for 24 GB cards and up. The fleet was on the class v4 rehearsal tonight.
|
||||
- **The hash loss while proving on Ampere and Ada**: the 5090's 4% is Blackwell with 32 GB; the 8 GB cards showed 6%; the 12 to 24 GB tiers are unmeasured and the hybrid row of 4.1 (a) moves with them.
|
||||
- **Price elasticity of job demand**: every demand count is an assumption. The customer brief's "low millions a year" is the only market figure and it is approximate.
|
||||
- **The rental market's supply curve**: 0 of 20 pods at the TH/s scale on 6 October; the attack costs assume the hash can be had at the measured price, which the bench entry says it cannot above about 2 GH/s.
|
||||
- **The Devnet 2 block-rate runs** (RUN_A, RUN_B) were empty at writing; a 10 BPS chain changes shards per segment, records per block and the per-block fee step, and 4.1 (e) should be re-read when they land.
|
||||
- **The economy simulator's price process** is exogenous; burns do not move it (4.2's burn is a number, not a feedback).
|
||||
- **BIP 8 and BIP 9 texts, the Ethereum specs, Monero's reward code, Aleo, Boundless and Succinct** are cited by repository and path from memory or from the project's earlier readings and are marked approximate throughout; no clone exists in `vendor/`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary for the coordinator
|
||||
|
||||
Lane 4 turned the eleven measured cards into a price per proof, built five demand curves with dollars and burn, re-ran the economy simulator with the measured table and the measured rental price under eight stresses, extended the security budget ten years with those fees, priced the dev fee and the signalling game, and tabulated what the other chains did. Three findings:
|
||||
|
||||
1. **The proving price is `h/N`, not "the cost of power".** Electricity is under a cent per billion cycles on every card; the price a prover must charge is the subsidy it forgoes, which is 100 to 300x Boundless's USD 0.21 at today's 1.16 GH/s and 0.2 to 0.4x at 100 GH/s for a card proving beside its miner (`utility.py` 1.3). And the adopted floor prices a job at 15 IGN per billion cycles, USD 0.075 / 0.30 / 1.50 at the three prices: above USD 0.014 per IGN the chain overprices the market by rule, and a two-week vote cannot follow a dollar market (proposal 1).
|
||||
2. **Fees are not a security budget for a decade.** All five uses together put USD 450 a day to miners and provers at launch and USD 4,200 in year 5 in the base scenario at 0.02 (`utility.py` 3.3) against USD 54,800 and 13,700 of daily emission; of the 48% fee share in year 5 under 1% reaches miners. The 20-day 34% weight attack costs 11.8x the honest fleet's 20 days at every price and year (`security_budget_10y.py`); its absolute net cost drops under USD 1 M in year 5 at 0.005 and year 9 at 0.02. The second audit has no payer by rule: the entity's own provers (USD 100 k a year at 5% of the pool, year 3, 0.02) are the only line that scales.
|
||||
3. **The 80/20 survives every stress but one, and that one strands the pool.** With the eleven measured cards, the 25-s window and a renter farm at the measured rent, T1 to T5 hold under price x10 and /10, external zero and x100, a 20% proving cartel and the first halving (`stress.py`, 8 scenarios x 3 seeds; hash troughs at 55% of pre-event under the /10 shock with 31% of cards off, the one row at the T1 line). A ten-day refusal by every prover breaks T4 only, and what it costs is 547,570 IGN a day of pool credit stranded in the escrow with no rule to return it (5.5 M IGN over the ten days): a silent supply cut nobody voted for (proposal 2). The renter farm is off in every row but the x10 price shock (margin +114%) and partly on under x100 external demand (-6%), which is lane 3's N_eq in an agent model.
|
||||
|
||||
Rules for main: the customer brief and the litepaper's "proofs at the cost of power" need the `h/N` condition before any customer conversation (proposal 3); `funding.md` section 4's dev-fee ceiling is a quarter too high (the fee moves the producer payout only); the three signalling thresholds are stated inconsistently across spec 5.7, CLAUDE.md, the P2 design and the litepaper's Mining section; and the spec is silent on pool credit nobody claims (proposal 2).
|
||||
389
docs/analysis/horizon/finality-and-weight.md
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
# Horizon lane 3: finality and weight
|
||||
|
||||
Date: 6 October 2026, evening UK (written 19:30Z to 21:00Z, while the live devnet's finality was paused). Lane: finality-and-weight (the measured behaviour of the weight rule and the designs that extend it; lane 1 holds the attack catalogue and the 51 percent paper, cross-referenced by name). Worktree: `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon`). Models: `sim/horizon/finality-and-weight/` (README there says how to run every number).
|
||||
|
||||
What was read: `docs/spec/03-finality.md` (whole, 3.11 included), `04-seeds-and-vdf.md`, `10-light-client.md`, `06-open-items.md` (O-3.1 to O-3.19), the fud-close worktree's `docs/spec/03-finality.md` 3.4.2 (the proposed vote and bitmap bounds, decided 6 Oct 2026 per `docs/plans/ledger-decisions.md` line 57); `docs/fud-ledger.md` F1 to F25 (F9, F14, F16, F18, F19, F20, F21 via its status lines, F22, F23, F24), P3, P4, P22, X20; `sim/README.md`, `sim/results_v2.md` (A to M), `sim/finality_v2.py` (this worktree's and fud-close `1544c63` with the block reading and scenario O); `docs/benchmarks/finality-v3-2026-10-04/` (fold-v2, fold-v3, split50-v2, split50-v3, split70-v3), `docs/benchmarks/round4-consensus-2026-10-04/results-final2.md`; `tools/finality-attacks/README.md`; `docs/plans/finality-v3-rollout-devnet.md`, `finality-v3-devnet-publish.md`; `docs/bench-log.md` entries of 4 to 6 October mentioning finality (floor 2/3, first live lock, rule v3, the C4 fix, round-4 items, the finality route, the rental cost of hash at line 2582); the gpu-fleet worktree's `docs/bench-log.md`, `docs/plans/`, `tools/fleet/` (grep for finality, lock, pause, voters, weight: the fleet has written no lock-delay or voter-count row yet; `docs/analysis/block-rate-devnet2.md` is still the template with RUN_A and RUN_B empty at 19:45Z), `docs/analysis/prover-tiers-real-cards.md`; the observer database (read-only SELECTs over `live_checkpoints`, `live_certificates`, `live_blocks`, `live_events`, `live_state`), node 1's log `/tmp/igneum-devnet/node1.out` on the Mac; `vendor/igneum-node` `Cargo.toml` and `consensus/core/src/finality.rs` for the BLS crate; the SP1 6.8.1 crates in the cargo registry (no SP1 clone exists under `vendor/`).
|
||||
|
||||
## 1. The three findings first
|
||||
|
||||
1. **Tonight's pause was the rule, not the aggregation path, and it was the frozen table that held it past 19:14Z.** The 20 keys that left the live chain between 17:20Z and 18:30Z held 3,026 of 7,083 blue blocks of the table frozen at the last lock (42.7 percent; the 13 that left with the 18:27 to 18:30Z rehearsal job alone 36.5 percent). The first unlocked checkpoint, 6843 at DAA 209,233 (about 18:40Z), had 75 of 93 voters' votes and 53.1 percent of total weight on the observer's node, under the two-thirds floor; certificates had kept forming for 18 checkpoints while node 1 and the observer were down (6824 to 6842, 18:30 to 18:39Z, 83 signers, 78.5 to 79.7 percent of total). From 19:14:53Z (checkpoint 6912) the stayers held 74.9 percent of the sliding table and still did not lock, because they hold 57.3 percent of the frozen table of lock 6842, which stands until DAA 216,402 (about 20:40Z). Under rule v2 the first lock would have come at 6912, 35 minutes after the last; under v3 the pause is one window, 2 hours on the devnet and 30 days on mainnet (spec 3.7 item 2, the price the project lead took on 4 October).
|
||||
2. **Of the four candidate rules, only the departure announcement keeps the one-third bound.** In the simulator (3 seeds, mainnet scale) the decaying denominator and the hysteresis floor both restore liveness after tonight's departure in under an hour and both reopen the partition double lock (fast decay: both sides of every 360-minute partition lock alone from minute 120, 467 to 473 conflicting locks in the 50/50 honest split and 17 to 264 in the poisoned eclipse; slow decay: both sides of the 12-day splits lock alone at day 0.5 to 0.9, 31,545 to 32,246 conflicts; hysteresis: a 20 percent equivocator conflicts from minute 60, 565 to 597 locks, the 13.3 percent bound of 3 October back). The leave rule locks 1 hour after the departure (0.04 days; under 4 devnet minutes) with 0 conflicting locks in every partition, eclipse and equivocator row, and an attacker who buys keys to make them leave gains nothing it would not get by signing with them (w + L must still reach 2/3). The two-tier report never conflicts in its final tier by construction and shows 516 to 1,062 conflicting PROVISIONAL locks in every 360-minute partition and about 34,000 in the 12-day splits, so it is a reporting layer with a health warning, not a rule.
|
||||
3. **Weight costs USD 8,424 x N x W / (1 - W) to rent for the full window** at the measured USD 11.7 per GH/s-hour: a veto (34 percent) against a 1 GH/s network is USD 4,300 over 30 days (0.52 x N of hash, a +52 percent step on the chart from day 1), against 1 TH/s USD 4.3 M; locking alone (67 percent) is 2.03 x N for 30 days (USD 17,100 per GH/s of network, USD 17 M at 1 TH/s), and faster is dearer (22 days: 10.6 x N). Buying old keys costs the seller's own rental equivalent, decays to nothing in 30 days (sim K), and nothing in the protocol makes weight unbuyable; what keeps the price at the rental cost is that the seller keeps a copy and one equivocation strips the key.
|
||||
|
||||
## 2. Method
|
||||
|
||||
Measured: the observer's Neon database (tables written by `tools/observer/observer.mjs`: `live_checkpoints` per index with state, signed and total weight, votes seen and voter count; `live_certificates` with the voter table and bitmap per certificate; `live_blocks` with `vote_key_hash` per block; `live_events`), read with SELECTs only through a scratchpad script (`fetch` to the Neon SQL endpoint, refusing any statement that is not SELECT; the queries are quoted inline). Node 1's log on the Mac for determination-to-lock delays (the `determined` and `LOCKED` lines per index; the log ends at 18:46:32Z when node 1 stopped). The departed keys were matched from certificate voter tables (48-byte public keys) to block producers (`vote_key_hash`) with BLAKE2b-256 keyed by `IgneumVoteKeyHash` (the fork's domain, `consensus/core/src/finality.rs`), 93 of 93 keys matched.
|
||||
|
||||
Simulated: `sim/horizon/finality-and-weight/finality_horizon.py`, a copy of `sim/finality_v2.py` (fud-close `1544c63`) with four candidate rules and three scenarios (T, P, Q), run on igneum-build-1 (`/srv/builds/horizon-finality-and-weight/sim/`, Python 3.12, numpy 1.26.4, `nice -n 19`, one process per candidate, seeds 7, 11 and 13, about 25 minutes wall while the box carried other agents' builds at load 20 to 60); the smoke run on the Mac under `tools/lock/with-lock.sh run`. The model is the one `sim/results_v2.md` describes (1,000 Pareto keys, three regions, 2-s inter-region delay, 97 and 99.5 percent uptime, no DAG) at mainnet scale (30-day window); the devnet's window is 7,200 DAA s, so a mainnet day is four devnet minutes. Arithmetic scripts: `weight_capture.py`, `lightclient_cost.py`.
|
||||
|
||||
Not run: the fast-time node harness (`tools/finality-attacks`) for the leave message (no such message exists in the node); any BLS timing on this machine (the figures are approximate from the crate's published benchmarks, anchored to the one measured pure-JavaScript verifier); the fleet's block-rate run (its file was still a template at 19:45Z).
|
||||
|
||||
## 3. Evidence
|
||||
|
||||
### 3.1 Tonight's pause, from the observer rows and node 1's log
|
||||
|
||||
Times UTC. DAA scores advance about 1 per second on the live devnet. The observer's node is the view throughout; "votes" are the votes that node had seen for the index.
|
||||
|
||||
| When | What | Source |
|
||||
|---|---|---|
|
||||
| 17:20 to 17:55Z | Four keys mine their last blocks on the live chain (92376b1a 105 blocks of the later frozen table, d1ee753c 22, 25dbfb0b 155, c4eb3431 131: 413 blocks, 5.8 percent) | `live_blocks`, max(received_at) per key before 18:43Z |
|
||||
| 18:27 to 18:30Z | Thirteen fleet keys mine their last blocks (d5996917 248, 39d2dafc 246, 52d9d8c8 236, 3ca93140 227, 8debbb2d 219, 92dabf9d 216, f155fac3 215, 6cd0935e 212, 0cf69e5c 208, 11047080 200, b42641ab 144, cf860e4d 121, 2aaa021b 91: 2,583 blocks, 36.5 percent of the frozen table): the class v4 rehearsal job stopping their miners | `live_blocks`; CLAUDE.md 6 Oct rules |
|
||||
| 18:30:02Z | Node 1's last own lock, 6823 (DAA 208,631), 78 signers, 68.7 percent of total; node 1 and the observer go down with the desktop app until 18:42:08Z (`Observer reconnected to the node`) | node1.out; `live_events` |
|
||||
| 18:30 to about 18:39Z | Locks 6824 to 6842 form without the hub (DAA 208,660 to 209,202): 83 signers, 78.5 to 79.7 percent of total; aggregators 3ca93140, 8debbb2d, 69570532 and the zero fallback | `live_checkpoints` (ingested at 18:42:09Z), `live_certificates` bitmaps |
|
||||
| about 18:39:40Z | The last lock, 6842 at DAA 209,202, 79 of 91 signers. Its frozen table (the voter table at 6850 the observer stored with it): 93 keys, 7,083 blocks; the 20 departed keys hold 3,026 (42.7 percent), the stayers 4,057 (57.3 percent) | `live_certificates` 6842 voters, matched to `live_blocks` |
|
||||
| about 18:40Z | 6843 at DAA 209,233 determined and never locked: 75 votes, 3,757 of 7,080 = 53.1 percent of total. The departed boxes' nodes have left the live chain (their blocks had already stopped), the signing weight is under two thirds: the rule pauses | `live_checkpoints` |
|
||||
| 18:42:08Z | The observer reconnects and the pause becomes visible on the hub; node 1 ingests 6828's certificate by gossip (`70.3% of the table frozen at lock 6827`) | `live_events`, node1.out |
|
||||
| about 18:50Z | Twenty indices without a lock (6862, DAA 209,800): `finality_active` false, reason `paused` (spec 3.9) | `live_checkpoints` |
|
||||
| 18:58 to 19:03Z | Votes seen fall to 49 (48.5 percent): five more keys quiet for five minutes (the hands' own restarts, approximate) | `live_checkpoints` 6876 to 6885 |
|
||||
| 19:14:53Z | 6912 at DAA 211,301: 79 votes, 5,355 of 7,152 = 74.9 percent of the SLIDING table, over two thirds; no lock. The stayers hold 57.3 percent of the table frozen at 6842 (Q5), under two thirds: "held by the frozen table" | `live_checkpoints`; spec Q5 |
|
||||
| 19:29Z (write-up) | Still paused: 6938 proposed at 79.2 percent with 78 votes. Expected first lock when the frozen table expires at DAA 216,402 (209,202 + 7,200), about 20:40Z, or when departed keys holding 9.4 points of the frozen table return | `live_checkpoints`; arithmetic |
|
||||
|
||||
Under rule v2 (sliding table only) the stayers' share rises as the departed blocks age out: from 53.1 percent at 6843 to two thirds after 7,200 x (1 - 1/(3 x 0.469)) = 2,082 DAA (spec 3.3.1's churn formula at the devnet window), which is checkpoint 6912 at 19:14:53Z: a 35-minute pause. Under v3 it is one window: 2 hours here, 30 days on mainnet (spec 3.7 item 2; `sim/results_v2.md` M4). The coordinator's working hypothesis of 19:3xZ (topology: the hub was down and the fleet's votes could not reach the VRF-picked aggregators) is refuted by three rows above: certificates formed while the hub was down (6824 to 6842), the zero-aggregator fallback of Q4 is in routine use (64 of the 251 certificates stored between 16:30 and 19:00Z name aggregator `00000000`, the next most frequent key 34), and the pause began at the checkpoint where the signing weight fell to 53.1 percent, which is the rule's threshold and not a routing failure. The observer's node itself held 74.9 percent of the sliding weight in votes from 19:14Z and did not certify, which only Q5 explains.
|
||||
|
||||
Per tier: a home miner, rig or pool user on the live devnet saw `finality_active` false for 2 hours and lost nothing (blocks, execution and payouts continued; the exchange guidance of 3.9 applies); a prover's records were still paid; the fleet operator learned that a standing box never leaves the live chain for an experiment (CLAUDE.md, the standing-fleet rule). On mainnet the same event, 43 percent of weight leaving in three minutes, is a 30-day pause under v3 and a 7.7-day one under v2.
|
||||
|
||||
### 3.2 Lock delay against voter count (measured)
|
||||
|
||||
Determination-to-lock on node 1 (the `determined` and `LOCKED` lines per index, 6 October 2026, per UTC hour; voter counts from the observer's `live_checkpoints` for the same hour).
|
||||
|
||||
| Voters above dust | UTC hours | Locks | Delay p50 | p90 | p99 | Max | Source |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 4 to 9 | 01 to 06 | 119 to 120 per hour | 0.86 to 0.97 s | 1.17 to 1.31 s | 1.53 to 1.80 s | 1.59 to 2.29 s | node1.out |
|
||||
| 17 to 24 | 07 to 11 | 118 to 120 | 0.82 to 0.97 s | 1.09 to 1.29 s | 1.53 to 1.83 s | 2.18 s (the 111-s p90 of 08Z is a restart) | node1.out |
|
||||
| 24 to 30 | 12 to 14 | 104 to 122 | 0.94 to 1.32 s | 1.36 s (quiet hours) | 48 s (restarts) | | node1.out |
|
||||
| 43 | 16 | 54 (hour cut by a restart) | 0.92 s | 1.17 s | | | node1.out |
|
||||
| 63 | 17 | 124 | 1.13 s | 1.44 s | 1.47 s | 8.8 s | node1.out |
|
||||
| 92 to 93 | 18 | 125 (to 18:30Z) | 1.26 s | 1.50 s | 1.82 s | 1.82 s | node1.out |
|
||||
| 6 (fast time, 300-ms proxied links) | 4 Oct | 11 to 12 per node | 1.008 s | | | | `docs/benchmarks/finality-v3-2026-10-04/fold-v3.md` |
|
||||
| 12 (cloud devnet, 5 locations) | 4 Oct | 212 indices | 1.24 s to the first certificate (p99 1.71 s), the last vote 1.45 s (p90 2.36 s) | | | | ledger F22, `infra/cloud-devnet/results/2026-10-04/f22-vote-timing.md` |
|
||||
| 1,000 (simulator, 2-s inter-region delay) | | 172,883 | 2.5 s | | 4.6 s | 6.1 s | `sim/results_v2.md` A |
|
||||
|
||||
The devnet's delay is the miner's 1-s template poll plus one gossip round (the 250-ms gossip pump per hop, ledger F22): 0.9 s at a handful of voters, 1.26 s at 93. A straight line through the devnet rows is 0.9 s + 4 ms per voter (approximate; 93 points on one topology), which puts 1,000 voters near 5 s and 8,192 near 34 s, past the 30-s interval. Section 4.3 says why that line does not hold and what does.
|
||||
|
||||
### 3.3 Sizes and crates (from the fork)
|
||||
|
||||
| Item | Value | Source |
|
||||
|---|---|---|
|
||||
| BLS crate | `blst = "0.3.17"` (workspace), min-pubkey variant: 48-byte G1 keys, 96-byte G2 signatures | `vendor/igneum-node/Cargo.toml` line 238, `consensus/core/Cargo.toml` line 20, `consensus/core/src/finality.rs` line 17 (`blst::min_pk`) |
|
||||
| Aggregation and verification | `AggregateSignature::aggregate` over the votes' signatures (line 230); `fast_aggregate_verify` over the summed public keys and one vote message (line 239 to 253) | `consensus/core/src/finality.rs` |
|
||||
| Vote item | 281 B (tag 1, index 8, checkpoint 32, key 48, signature 96, sortition proof 96) | spec 3.4.2 item 1 (fud-close), the fork's `Vote::LEN` |
|
||||
| Certificate | 273 B plus ceil(V/8) B of bitmap: 285 B at 93 voters, 398 at 1,000, 1,297 at 8,192, 8,465 at 65,536 | same |
|
||||
| Bitmap wire bound | 1 MiB today (`Certificate::read`, `bitmap_len > 1 << 20`, line 429); 8,192 B proposed and decided (65,536 voters, 8x the S2 switch) | finality.rs; spec 3.4.2 item 3; ledger-decisions line 57 |
|
||||
| Per-block vote bound | 48 on the devnet; 384 on mainnet proposed and decided (107,904 B, 21.6 percent of the compute mass; a checkpoint's 8,192 votes drain in 21.3 blocks) | spec 3.10 Q1/Q2 row; 3.4.2 item 2 |
|
||||
| Votes per checkpoint, single-vote carriage | 93 voters: 26,133 B; 1,000: 281,000 B; 8,192: 2,301,952 B (4.6x one block's mass); 65,536: 18.4 MB | 281 x V |
|
||||
| Votes per checkpoint, aggregated carriage (Q2 allows one BLS signature plus a bitmap per (index, hash)) | about 0.4 KB at 93 voters, 1.2 KB at 8,192, 8.6 KB at 65,536 | spec 3.4.2 item 2 |
|
||||
|
||||
### 3.4 What the simulator already measured (cited, `sim/results_v2.md`)
|
||||
|
||||
| Fact | Value | Section |
|
||||
|---|---|---|
|
||||
| Churn under v2: first lock after a set holding x stops mining and signing | 35 percent: 1.7 days (analytic 1.4); 50 percent: 10.1 to 10.3 days (analytic 10.0) | L2, D |
|
||||
| Churn under v3 | 30.00 days at 35 and 50 percent (the frozen table's expiry) | M4 |
|
||||
| Silent set that keeps mining | 34 percent and above: no lock for as long as it is silent; first lock 0 min after it returns | J, L1 |
|
||||
| Equivocator across a 50/50 split | 33 percent: 0 conflicts; 34 percent: 2 to 54 conflicts from minute 2 to 77 (the one-third bound) | H, M5 |
|
||||
| Long honest partition, view-local weight, v2 against v3 | 50/50: both sides lock alone from day 10.1 to 10.3 under v2, never in 12 days under v3, both at day 30.00 of a 31-day split | L4, M2, M3 |
|
||||
| Acquired keys worth 40 percent, attacker at 30 percent of hash | veto from day 1 to day 19 or 20, 30 percent on day 30; silent, 63,307 to 68,716 of 86,400 checkpoints stalled | K |
|
||||
|
||||
## 4. Model
|
||||
|
||||
### 4.1 The pause arithmetic (spec 3.3.1 and 3.7, restated with tonight's inputs)
|
||||
|
||||
Let x be the share of the window weight that stops mining and signing at once, W the window (2,592,000 DAA s on mainnet, 7,200 on the devnet).
|
||||
|
||||
| Rule | First lock after the departure | Tonight (x = 0.469 on the observer's node at 6843, W = 7,200) | Mainnet, same x |
|
||||
|---|---|---|---|
|
||||
| v2, sliding table | W x (1 - 1/(3x)) (never for x at or under 1/3) | 2,082 DAA, 35 min: measured as the moment the sliding share crossed two thirds (6912) | 7.7 days |
|
||||
| v3, frozen table (live) | W after the last lock, whatever x over 1/3 | 7,200 DAA, 2 h (expected 20:40Z) | 30 days |
|
||||
| (iv) leave, delay D | D after the signed leave (0 if sent D before the stop) | 1 h, or 0 with notice | 1 h |
|
||||
| (i) decay, grace T, rate r per hour | at most T + (1/r) x (1 - (1 - x)/(2x)) hours: the departed weight decays until the stayers hold two thirds of what is left | T 1 h, r 0.5: 1 h 17 min (x 0.469); T 6 h, r 1/24: 20 h | the same hours |
|
||||
| (iii) hysteresis, H hours, low floor f | H hours, then only if the stayers hold f x 2/3 of total | f = 0.85: 56.7 percent needed, the stayers held 53.1 then 57.3 percent: after H plus the ageing to 56.7 percent, 1 to 1.5 h | about 1 day |
|
||||
| (ii) two-tier | provisional at once (2/3 of the active denominator); final as v3 | provisional 0 min, final 2 h | provisional minutes, final 30 days |
|
||||
|
||||
### 4.2 Why the view-dependent candidates fail (and the sim's confirmation)
|
||||
|
||||
Spec 3.11.2's bound comes from counting: two certificates at one index need 2/3 of the denominator each, 4/3 in all, so a third signed both and that third is equivocating. The denominator has to be the same number on both sides of a partition for that sum to mean anything. A rule that removes weight on what a view has not seen (a vote missing for T hours, blocks missing) gives each side a different denominator: side A removes side B's keys, side B removes A's, and both sides' own share rises toward 1 at the same rate. For a 50/50 split under decay(T, r) each side's own share reaches 2/3 when the other side's factor is 0.5, at T + 1/(2r) hours (2 hours at T 1 h, r 0.5; 18 hours at T 6 h, r 1/24), and every checkpoint after that is a conflicting lock, the hazard of `sim/results_v2.md` E in a new coat. The frozen table does not save it when the decay is applied to the frozen table too (which is the only way decay helps tonight). The hysteresis floor is view-dependent in the same way (each side measures its own connected share), so after H hours both sides run the 0.85 rule and the 13.3 percent equivocator bound of 3 October returns (the sim's 33 percent row under `hyst` shows one side locking at minute 59). A rule that removes weight on what a view has seen, a signed leave carried in the DAG or equivocation evidence, is seen by both sides at the heal and by at most one side during the split; during the split the side that saw the leave removes L from its denominator and needs 2/3 (1 - L) of signers while the other side still needs 2/3 of the full table; both locking needs s_A + s_B at least 2/3 (2 - L), more than the 1 - L available without an equivocator, so the one-third bound survives (with an equivocator a, the bound is a at least (1 - L)/3 of the remaining weight, the same statement over the reduced table).
|
||||
|
||||
Why leaving bought keys buys nothing (Q2 in the sim and arithmetic): an attacker holding w of the window who buys L and makes it leave holds w / (1 - L) of what remains; to lock alone it needs w at least 2/3 (1 - L), so w + L at least 2/3 + L/3, never under two thirds of the window, and signing with the bought keys (w + L at least 2/3) is the cheaper use of the same purchase. In the model the attacker's share after leaving its bought 40 percent was 4.8 percent on day 3, the position of its own hash alone.
|
||||
|
||||
### 4.3 Lock delay as a function of voters and message delay
|
||||
|
||||
delay = template poll (1 s on the devnet; the node's own determination on mainnet, 0) + hop_1 (block to voter) + hop_2 (vote to aggregator) + processing + certificate gossip (one hop). The simulator's two hops at Delta give median 0.7 s at 0.5 s, 2.5 s at 2 s, 6.2 s at 5 s (A); the devnet's 0.9 s is the poll plus a 250-ms pump. The per-voter term is the aggregator's verification of each vote as it arrives: one BLS verify is a pairing, about 1.6 ms (approximate, blst 0.3.17 published figures; the fork verifies each vote on ingest, `verify_vote_signature`, finality.rs line 192), which is 150 ms per checkpoint at 93 voters, 1.6 s at 1,000, 13 s at 8,192 and 105 s at 65,536 on one core: past 1,000 voters the single-vote path is a CPU bound before it is a bandwidth bound, and at 8,192 it is more than a quarter of every core's time on every node (every node verifies every vote it relays). The fix is already in the spec's text (Q2 aggregated carriage) and in the crate (`AggregateSignature::aggregate` then one `fast_aggregate_verify`): aggregate first, verify once per (index, hash), which costs V G1 additions (about 1 us each) plus one pairing: 1.7 ms at 93, 2.6 ms at 1,000, 10 ms at 8,192, 67 ms at 65,536. Its price is the batch-poisoning vector (one invalid vote fails the batch and forces bisection); S2's sub-user sortition at 8,192 bounds the signer count at about 4,000 expected either way.
|
||||
|
||||
| Voters | Vote bytes per checkpoint (single) | Verify per checkpoint, single votes (approximate) | Aggregate-first (approximate) | Lock delay, model (Delta 2 s) | Bitmap |
|
||||
|---|---|---|---|---|---|
|
||||
| 12 | 3.4 KB | 19 ms | 1.6 ms | 2.5 s (sim A, 1,000 keys) ; 1.24 s measured | 2 B |
|
||||
| 40 | 11 KB | 64 ms | 1.6 ms | about 2.5 s | 5 B |
|
||||
| 100 | 28 KB | 160 ms | 1.7 ms | about 2.6 s; 1.26 s measured at 93 on the devnet | 13 B |
|
||||
| 1,000 | 281 KB | 1.6 s | 2.6 ms | about 4 s single, 2.5 s aggregated | 125 B |
|
||||
| 8,192 | 2.3 MB (4.6x block mass) | 13 s per node per checkpoint: breaks the 30-s cadence on a shared core | 10 ms | 2.5 s aggregated; S2 switches to about 4,000 sub-users here | 1,024 B |
|
||||
| 65,536 | 18.4 MB | 105 s: impossible single | 67 ms (3.9 s of key decompression once) | 2.5 s aggregated | 8,192 B, the proposed wire bound |
|
||||
|
||||
Where the aggregator path breaks down: not at the 8 VRF-picked aggregators (anyone MAY aggregate, Q4's fallback at 15 DAA is in routine use tonight: 26 percent of certificates) but at per-vote verification above about 1,000 voters and at the per-block vote carriage above 8,192 (spec 3.4.2 item 2's 384-per-block bound drains a checkpoint in 21 blocks; participation accounting lags, locks do not, because certificates form from gossiped votes). The message-delay term scales the two hops and nothing else; at 5 s inter-region delay the slowest region already loses participation to the 15-s grace (A).
|
||||
|
||||
### 4.4 Weight capture cost (task 3; `weight_capture.py`)
|
||||
|
||||
share(t) = (t/30) x A/(N + A) for A rented against N for t days (spec 3.1, sim B within 0.04 points). To hold W at day t: A = N q/(1 - q), q = 30W/t (needs t over 30W). Cost = A x t x 24 x USD 11.7 per GH/s-hour (measured 6 Oct 2026, bench-log "Rental cost of hash": 1,748 MH/s for USD 20.44/h on RunPod community pods; the 8x 4090 rig USD 5.92/h for 459 MH/s). Cost falls with t, so the cheapest attack takes the full window: A = N W/(1 - W), cost = 8,424 x N x W/(1 - W) USD per GH/s of network.
|
||||
|
||||
| Target | Hash to rent | Day noticed (the chart step) | N = 1 GH/s | N = 10 GH/s | N = 100 GH/s | N = 1 TH/s |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 34 percent in 30 days (veto) | 0.52 x N | day 1: +52 percent | USD 4,300 | USD 43 k | USD 434 k | USD 4.3 M |
|
||||
| 34 percent in 21 days | 0.94 x N | day 1: +94 percent | USD 6 k | USD 56 k | USD 557 k | USD 5.6 M |
|
||||
| 51 percent in 30 days | 1.04 x N | +104 percent | USD 8.8 k | USD 88 k | USD 877 k | USD 8.8 M |
|
||||
| 67 percent in 30 days (locks alone) | 2.03 x N | +203 percent | USD 17 k | USD 171 k | USD 1.71 M | USD 17.1 M |
|
||||
| 67 percent in 25 days | 4.10 x N | +410 percent | USD 29 k | USD 288 k | USD 2.9 M | USD 28.8 M |
|
||||
| 67 percent in 22 days | 10.6 x N | +1,058 percent | USD 65 k | USD 654 k | USD 6.5 M | USD 65 M |
|
||||
|
||||
What the market supplies: RunPod gave 0 of 20 pods asked at 18:59Z to 19:15Z (bench-log); 38 pods were 1.75 GH/s. So at tonight's 1.16 GH/s devnet every row of the first column is a dinner; at 100 GH/s the 52 GH/s for a veto did not exist on the one market asked (approximate). The alarm that sees the step is lane 1's detector.
|
||||
|
||||
Buying old keys (F19): a key is a 32-byte scalar named in headers by `vote_key_hash`; it can be handed over, W5 succession moves its history once (not implemented, O-3.11), and the seller can keep a copy. Its worth is its blocks: keys worth b of the window are the position of having rented b/(1 - b) x N for 30 days (USD 2,100 per GH/s of network at b 20 percent, 4,300 at 34, 5,600 at 40), and that position decays as b (1 - t/30) + r t/30 (sim K, within 0.6 points). What makes weight unbuyable: nothing in the protocol. What makes bought weight a bad buy: it ages out in 30 days whatever the buyer does, a seller's copy can equivocate it away (3.6), and a pool's key is its payout identity, so the price is the pool. What the header does not do: it does not tell anyone the key changed hands until its blocks stop matching its old profile (the detector's job). A rule that would make it harder, decaying a key whose block profile breaks, is view-dependent in a partition (section 4.2) and is not recommended.
|
||||
|
||||
Per tier: a home miner's or rig's key (one per machine under 3.4.2 item 4) is worth its 30 days of blocks and nothing a buyer would pay for; a pool's key is the only one worth buying and the only one whose sale is visible; a holder's finality rests on the 2/3 of weight no one can rent cheaply past a few GH/s; a rollup customer's bridge inherits the same bound.
|
||||
|
||||
### 4.5 Long-range and checkpoint sync for light clients (task 4; `lightclient_cost.py`)
|
||||
|
||||
What a node joining after 60 days trusts (spec 10.1 and 10.3): the trusted checkpoint shipped in its release, refreshed from N of M seed nodes (M 5, N 3, O-10.5), and from there every certificate it fetches is verified against the voter set, which in checkpoint mode it takes from nodes (N of M agreement) and in full-header mode recomputes from 30 days of headers (W2). The cold-sync node of X20 selects the heaviest DAG then follows certificates found in it (F5), so its first 30 days of history are proof of work in the sense of 3.9. The weak-subjectivity window Igneum has in fact is one weight window: a certificate older than 30 days can be checked only against a voter table the client cannot recompute from less than 30 days of headers, and under v3 the frozen table expires 30 days after a lock, so a node offline longer than 30 days cannot tell a certified chain from a chain certified by keys that have since left the window; the same class of assumption as Ethereum's weak-subjectivity period for a sync-committee checkpoint (not cloned here; approximate), with the window the parameter.
|
||||
|
||||
| Mode, per year of chain | 93 voters | 1,000 | 8,192 | 65,536 | Source |
|
||||
|---|---|---|---|---|---|
|
||||
| Every certificate (1,051,200) plus its header, bytes | 720 MB | 839 MB | 1.78 GB | 9.3 GB | 285 to 8,465 B per certificate plus 400 B header |
|
||||
| Verify time, one laptop core (approximate: V G1 adds plus hash-to-G2 plus two pairings, 1.8 to 67 ms each) | 32 min | 48 min | 2.9 h | 20 h | `lightclient_cost.py` |
|
||||
| On a phone core (3x, approximate) | 1.6 h | 2.4 h | 8.7 h | 59 h | |
|
||||
| One certificate per presence window (4,380 a year, spec 10.3 item 3) | 3.0 MB, 8 s | 3.5 MB, 12 s | 7.4 MB, 44 s | 39 MB, 4.9 min | the voter set at each stop is not paid for |
|
||||
| Full-header mode, headers alone | 12.6 GB a year at 400 B per header | | | | |
|
||||
|
||||
The measured anchor: the pure-JavaScript verifier of a 16-signer certificate took 58 to 68 ms warm on the M5 Max (bench-log, sweep round 6, P3), about 30x the native estimate here; a phone has not been measured (O-10.3).
|
||||
|
||||
The ZK light client (phase two, O-10.8), designed: one recursive proof per checkpoint whose step statement is "certificate i verifies under voter table T_i; T_i follows from T_(i-1) by the interval's 30 blue headers and the window's ageing; the signers hold at least two thirds of T_i and of the frozen table; C_i's selected chain passes through C_(i-1)", with the previous step's proof verified inside (SP1's deferred-proof path, `VERIFY_SP1_PROOF` in `sp1-core-executor-6.8.1/src/syscall_code.rs`; the recursion crates are `sp1-recursion-{circuit,compiler,executor,machine,gnark-ffi}-6.8.1` in the cargo registry, no SP1 clone under `vendor/`). What the circuit costs, approximate: key aggregation V x BLS12381_ADD (about 500 cycles each: 46,500 cycles at 93 voters, 0.5 M at 1,000, 4.1 M at 8,192); hash-to-G2 about 0.3 M (SHA-256 is precompiled, the Fp2 arithmetic is BLS12381_FP2_*); the two pairings 10 to 30 M cycles, the dominant term, because 6.8.1 has Fp and Fp2 precompiles for BLS12-381 (ADD, DOUBLE, FP_ADD/SUB/MUL, FP2_ADD/SUB/MUL) and no pairing precompile; 30 BLAKE2b header hashes and the table transition 1 to 2 M (no BLAKE2b precompile). Against the measured shard curve (`docs/analysis/prover-tiers-real-cards.md`: 4.7 M cycles compressed in 4.8 to 14.4 s alone, 10.7 to 37.5 s beside the miner) a 15 to 35 M cycle step is 15 to 100 s alone and 40 to 260 s beside a miner, plus the recursion step measured at 2.2 to 2.5 s idle and 7.9 to 9.7 s beside the miner on the 5090 (bench-log, agg-cost and `chain-pc2-pv1c`). One checkpoint every 30 s therefore needs 1 to 4 proving-only cards (or 2 to 9 mining ones) at it continuously. A Groth16 wrap for the phone is the unbuilt R4 (P3).
|
||||
|
||||
What it buys each tier: a phone wallet verifies one wrapped proof per open (about 400 B, milliseconds once the wrapper exists) instead of a certificate chain and a trusted voter set, and the "voter set: from nodes" status disappears (spec 10.4); a bridge verifies one proof per checkpoint it settles on and never a BLS certificate on-chain (an on-chain BLS12-381 aggregate verify at 1,000 voters is about 1,000 G1 additions and one pairing, which on Ethereum is the point-evaluation and pairing precompile budget, approximate); a rollup customer gets a finality statement its own verifier can check without Igneum's voter list; a node operator pays nothing (full nodes keep the native rule); a prover tier gains a steady job (one proof per 30 s) at the cycle counts above; a home miner with one 12 GB card beside its miner (27 to 37 s per 4.7 M-cycle shard) cannot keep up with a 30-s cadence alone and joins as one of several; a 24 or 32 GB card alone does it in the interval.
|
||||
|
||||
### 4.6 Prover attestations as a second finality leg (task 5)
|
||||
|
||||
Design: a checkpoint locks when (a) its certificate carries two thirds of weight (Q3, Q5) AND (b) proof records covering every chain block in (C_(i-1), C_i] from at least k distinct prover keys are in the past of some block the certificate's signers could see. Measured inputs: the proof lag on the live devnet, block to carried record, p50 44 s, p90 52 s, p99 62 s, max 65 s (bench-log, proving v1 coverage windows, 5 Oct); coverage 2.4 to 4.7 percent of blocks with one prover (the same rows); the chain-mode cost 17 s per empty block on a mining 5090, about 5 s proving-only; a 12 GB card beside its miner 27 to 37 s per v1 shard (prover-tiers); a mandatory rule needs about 6 proving-only 5090s or 18 mining ones for an empty-block chain at 1 block/s (bench-log table), 45 proving-only at B_p.
|
||||
|
||||
| Measure | Weight alone (today) | Weight AND k-prover attestations | Label |
|
||||
|---|---|---|---|
|
||||
| Lock delay after the checkpoint block | 1.26 s at 93 voters (3.2) | at least the slowest block's proof lag inside the interval: p99 62 s today, so about 60 to 70 s; the transaction-to-lock figure of C1 rises from 90 to 120 s to about 150 to 190 s | measured lag, derived sum |
|
||||
| Checkpoints that could lock on tonight's devnet | all with two thirds signing | 2.4 to 4.7 percent (one prover): finality paused 95 percent of the time until proving is mandatory and the fleet is 6 to 18 cards | measured coverage |
|
||||
| What it stops that weight does not | nothing for a full node: it re-executes and vetoes a statement that is not the native one (spec 7.2 item 5, the native veto) | a two-thirds weight holder cannot lock a checkpoint whose execution has no valid proof, which protects the LIGHT client, who trusts certificates and cannot execute (10.1); the design already gives the light client that by requiring the segment proof beside the certificate (10.4 item 4), so the leg moves the requirement from the client into the lock | design |
|
||||
| Withholding to pause | a silent third pauses (L1) | a prover set that withholds proofs pauses finality for as long as no one else proves; the shard sortition names 8 provers by weight with a 10-s exclusive window and then anyone MAY prove (spec 7.2), so the price of a pause is out-proving every honest card for the whole pause, which in a thin market (tonight: one prover at times) is one card's outage | design, measured market |
|
||||
| Per tier | unchanged | a 12 GB card beside its miner proves one 4.7 M-cycle shard in 27 to 37 s, so k = 2 provers per block means 37k mining 12 GB cards (or 10k proving-only 4070s at 12 s) kept busy for an empty chain, approximate; a pool user nothing; a holder a longer wait; a rollup customer the same proof it already needs | prover-tiers, derived |
|
||||
|
||||
Verdict: not as a lock condition now. The leg converts "locked" into "locked and proven" at the cost of a minute of lock delay and a pause whenever proving coverage drops, which tonight is almost always. The design's four-state interface (included, executed, proven, locked; O-7.2) already gives the exchange and the wallet the conjunction as a reading. Gate before it could become a rule: 99 percent of chain blocks proven within 60 s for 7 days on the public testnet with at least 3 distinct provers per block, measured by `tools/proving-v1/coverage.mjs`.
|
||||
|
||||
## 5. Results of the candidate runs (`finality_horizon.py`, seeds 7, 11, 13)
|
||||
|
||||
### 5.1 T. Tonight's departure: first lock after x of weight stops mining and signing at once (31 days, seeds 7, 11, 13)
|
||||
|
||||
| departed weight | rule | first final lock after the departure | provisional tier | stalled checkpoints | stalled after the first lock | conflicting final locks | conflicting provisional locks |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 34% | v2 | 0.77 to 0.87 d (devnet 3 to 3 min) | | 4057 to 4727 | 1531 to 2489 | 0 | |
|
||||
| 34% | v3 | 30.00 d (devnet 120 min) | | 86388 to 86472 | 0 to 31 | 0 | |
|
||||
| 34% | leave | 0.04 to 0.04 d (devnet 0 to 0 min) | | 119 to 521 | 0 to 398 | 0 | |
|
||||
| 34% | decay1 | 0.05 to 0.05 d (devnet 0 to 0 min) | | 133 to 534 | 0 to 398 | 0 | |
|
||||
| 34% | decay6 | 0.30 to 0.30 d (devnet 1 to 1 min) | | 897 to 1298 | 24 to 442 | 0 | |
|
||||
| 34% | hyst | 0.04 to 0.04 d (devnet 0 to 0 min) | | 881 to 1445 | 761 to 1325 | 0 | |
|
||||
| 34% | twotier | 30.00 d (devnet 120 min) | provisional 0.000 to 0.004 d (devnet 0.0 to 0.0 min) | 86388 to 86472 | 0 to 31 | 0 | 0 |
|
||||
| 45% | v2 | 7.85 to 8.03 d (devnet 31 to 32 min) | | 23947 to 25153 | 1369 to 2065 | 0 | |
|
||||
| 45% | v3 | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
|
||||
| 45% | leave | 0.04 d (devnet 0 min) | | 119 to 204 | 0 to 83 | 0 | |
|
||||
| 45% | decay1 | 0.07 to 0.08 d (devnet 0 to 0 min) | | 224 to 296 | 9 to 83 | 0 | |
|
||||
| 45% | decay6 | 0.64 to 0.67 d (devnet 3 to 3 min) | | 1946 to 1985 | 0 to 129 | 0 | |
|
||||
| 45% | hyst | 30.00 d (devnet 120 min) | | 86382 to 86420 | 0 to 22 | 0 | |
|
||||
| 45% | twotier | 30.00 d (devnet 120 min) | provisional 0.031 to 0.034 d (devnet 0.1 to 0.1 min) | 86382 to 86420 | 0 to 22 | 0 | 0 |
|
||||
| 50% | v2 | 10.09 to 10.34 d (devnet 40 to 41 min) | | 30107 to 31264 | 1065 to 1456 | 0 | |
|
||||
| 50% | v3 | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
|
||||
| 50% | leave | 0.04 d (devnet 0 min) | | 118 to 490 | 0 to 371 | 0 | |
|
||||
| 50% | decay1 | 0.08 to 0.09 d (devnet 0 to 0 min) | | 244 to 609 | 0 to 371 | 0 | |
|
||||
| 50% | decay6 | 0.76 to 0.77 d (devnet 3 to 3 min) | | 2231 to 2543 | 0 to 371 | 0 | |
|
||||
| 50% | hyst | 30.00 d (devnet 120 min) | | 86387 to 86514 | 0 to 10 | 0 | |
|
||||
| 50% | twotier | 30.00 d (devnet 120 min) | provisional 0.042 d (devnet 0.2 min) | 86387 to 86514 | 0 to 10 | 0 | 0 |
|
||||
|
||||
### 5.2 P1. Partitions of 360 minutes (each side retargets and counts only its own blocks)
|
||||
|
||||
| rule | case | partition min | conflicting final locks | conflicting provisional locks | first conflict, min | first lock per side, min | every pre-heal lock kept | first lock after heal, min | stalls in 2 h after heal |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| v2 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| v2 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| v2 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / 279 (never in 2 of 3) | yes | 0 to 0 | 0 |
|
||||
| v2 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 142 to 291 | | 14 to 77 | 11 to 48 / 0 to 76 | yes | 0 to 0 | 0 |
|
||||
| v2 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
|
||||
| v2 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
|
||||
| v2 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
|
||||
| v3 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| v3 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| v3 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
|
||||
| v3 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
|
||||
| v3 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
|
||||
| v3 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
|
||||
| v3 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
|
||||
| leave | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| leave | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| leave | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
|
||||
| leave | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
|
||||
| leave | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
|
||||
| leave | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
|
||||
| leave | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
|
||||
| decay1 | 50/50 honest, 0% attacker | 360 | 467 to 473 | | 121 to 126 | 120 to 122 / 121 to 123 | yes | 0 | 0 |
|
||||
| decay1 | 50/50 + 20% equivocator (sides 60/60) | 360 | 528 to 535 | | 93 to 94 | 91 / 92 to 94 | yes | 0 | 0 |
|
||||
| decay1 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 587 to 595 | | 64 to 65 | 62 / 63 to 65 | yes | 0 to 0 | 0 |
|
||||
| decay1 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 575 to 612 | | 14 to 62 | 12 to 50 / 0 to 62 | yes | 0 to 0 | 0 |
|
||||
| decay1 | 40/40/20 honest | 360 | 815 to 822 | | 142 to 144 | 140 to 142 / 140 to 140 / 166 to 166 | yes | 0 to 0 | 0 |
|
||||
| decay1 | 60/40 honest | 360 | 435 to 436 | | 142 to 142 | 92 to 96 / 141 to 142 | yes | 0 to 0 | 0 |
|
||||
| decay1 | 70/30 honest | 360 | 403 to 414 | | 154 to 156 | 0 to 4 / 154 to 156 | yes | 0 | 0 |
|
||||
| decay6 | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| decay6 | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| decay6 | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
|
||||
| decay6 | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
|
||||
| decay6 | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
|
||||
| decay6 | 60/40 honest | 360 | 0 | | never | never / never | yes | 0 to 0 | 0 |
|
||||
| decay6 | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
|
||||
| hyst | 50/50 honest, 0% attacker | 360 | 0 | | never | never / never | yes | 0 | 0 |
|
||||
| hyst | 50/50 + 20% equivocator (sides 60/60) | 360 | 565 to 597 | | 60 to 61 | 58 to 61 / 58 to 61 | yes | 0 | 0 |
|
||||
| hyst | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 596 to 603 | | 60 to 62 | 59 to 60 / 60 to 62 | yes | 0 to 0 | 0 |
|
||||
| hyst | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 160 to 342 | | 14 to 60 | 12 to 50 / 0 to 60 | yes | 0 to 0 | 0 |
|
||||
| hyst | 40/40/20 honest | 360 | 0 | | never | never / never / never | yes | 0 to 0 | 0 |
|
||||
| hyst | 60/40 honest | 360 | 0 | | never | 58 to 61 / never | yes | 0 to 0 | 0 |
|
||||
| hyst | 70/30 honest | 360 | 0 | | never | 0 to 4 / never | yes | 0 | 0 |
|
||||
| twotier | 50/50 honest, 0% attacker | 360 | 0 | 589 to 603 | never | never / never | yes | 0 | 0 |
|
||||
| twotier | 50/50 + 20% equivocator (sides 60/60) | 360 | 0 | 653 to 660 | never | never / never | yes | 0 | 0 |
|
||||
| twotier | 50/50 + 33% equivocator (sides 66.5/66.5) | 360 | 0 | 705 to 715 | never | never / never | yes | 0 to 0 | 0 |
|
||||
| twotier | 50/50 + 34% equivocator (sides 67/67, the bound) | 360 | 139 to 283 | 713 to 720 | 14 to 78 | 12 to 50 / 0 to 77 | yes | 0 to 0 | 0 |
|
||||
| twotier | 40/40/20 honest | 360 | 0 | 1056 to 1062 | never | never / never / never | yes | 0 to 0 | 0 |
|
||||
| twotier | 60/40 honest | 360 | 0 | 516 to 564 | never | never / never | yes | 0 to 0 | 0 |
|
||||
| twotier | 70/30 honest | 360 | 0 | 523 to 532 | never | 0 to 4 / never | yes | 0 | 0 |
|
||||
|
||||
|
||||
P2. The poisoned eclipse (a 34% attacker plus a 20% pool; the eclipsed side holds 54% of total)
|
||||
|
||||
| rule | eclipse h | conflicting final locks | conflicting provisional locks | first conflict, min | locks on the eclipsed side | honest-side stalls during | stalls after heal |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| v2 | 1 | 0 | | never | 0 | 0 | 0 |
|
||||
| v2 | 2 | 0 | | never | 0 | 0 | 0 |
|
||||
| v2 | 4 | 0 | | never | 0 | 0 | 0 |
|
||||
| v3 | 1 | 0 | | never | 0 | 0 | 0 |
|
||||
| v3 | 2 | 0 | | never | 0 | 0 | 0 |
|
||||
| v3 | 4 | 0 | | never | 0 | 0 | 0 |
|
||||
| leave | 1 | 0 | | never | 0 | 0 | 0 |
|
||||
| leave | 2 | 0 | | never | 0 | 0 | 0 |
|
||||
| leave | 4 | 0 | | never | 0 | 0 | 0 |
|
||||
| decay1 | 1 | 0 | | never | 0 | 0 | 0 |
|
||||
| decay1 | 2 | 17 to 24 | | 109 to 111 | 22 to 24 | 0 | 0 |
|
||||
| decay1 | 4 | 257 to 264 | | 109 to 111 | 263 to 265 | 0 | 0 |
|
||||
| decay6 | 1 | 0 | | never | 0 | 0 | 0 |
|
||||
| decay6 | 2 | 0 | | never | 0 | 0 | 0 |
|
||||
| decay6 | 4 | 0 | | never | 0 | 0 | 0 |
|
||||
| hyst | 1 | 0 | | never | 0 | 0 | 0 |
|
||||
| hyst | 2 | 0 | | never | 0 | 0 | 0 |
|
||||
| hyst | 4 | 0 | | never | 0 | 0 | 0 |
|
||||
| twotier | 1 | 0 | 19 to 24 | never | 0 | 0 | 0 |
|
||||
| twotier | 2 | 0 | 137 to 145 | never | 0 | 0 | 0 |
|
||||
| twotier | 4 | 0 | 377 to 385 | never | 0 | 0 | 0 |
|
||||
|
||||
|
||||
P3. Long honest partitions with view-local weight, 12 days
|
||||
|
||||
| rule | honest split | days | first lock per side, day | conflicting final locks | conflicting provisional locks | first conflict | every pre-heal lock kept | first lock after heal, min |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| v2 | 50/50 | 12 | 10.15 to 10.18 / 10.12 to 10.34 | 2890 to 3550 | | 10.22 to 10.35 d | yes | 0 |
|
||||
| v2 | 60/40 | 12 | 5.15 to 5.27 / never | 0 | | never | yes | 0 to 0 |
|
||||
| v3 | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
|
||||
| v3 | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
|
||||
| leave | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
|
||||
| leave | 60/40 | 12 | never / never | 0 | | never | yes | 0 to 0 |
|
||||
| decay1 | 50/50 | 12 | 0.08 to 0.08 / 0.08 to 0.09 | 34146 to 34212 | | 0.08 to 0.09 d | yes | 0 |
|
||||
| decay1 | 60/40 | 12 | 0.06 to 0.07 / 0.10 to 0.10 | 33931 to 34254 | | 0.10 to 0.10 d | yes | 0 to 0 |
|
||||
| decay6 | 50/50 | 12 | 0.77 to 0.77 / 0.75 to 0.78 | 32120 to 32246 | | 0.77 to 0.78 d | yes | 0 |
|
||||
| decay6 | 60/40 | 12 | 0.52 / 0.92 to 0.93 | 31545 to 31873 | | 0.93 to 0.93 d | yes | 0 to 0 |
|
||||
| hyst | 50/50 | 12 | never / never | 0 | | never | yes | 0 |
|
||||
| hyst | 60/40 | 12 | 0.04 to 0.04 / never | 0 | | never | yes | 0 to 0 |
|
||||
| twotier | 50/50 | 12 | never / never | 0 | 34267 to 34418 | never | yes | 0 |
|
||||
| twotier | 60/40 | 12 | never / never | 0 | 34094 to 34381 | never | yes | 0 to 0 |
|
||||
|
||||
### 5.3 Q1. Silent weight that keeps mining for 6 hours, then resumes
|
||||
|
||||
| rule | silent weight | silent hours | first lock after the stop, min | stalled while silent | longest gap, min | first lock after resume, min | conflicting locks |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| v2 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
|
||||
| v2 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| v2 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| v3 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
|
||||
| v3 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| v3 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| leave | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
|
||||
| leave | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| leave | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| decay1 | 30% | 6 | 0 | 0 | 1 | 0 to 0 | 0 |
|
||||
| decay1 | 34% | 6 | 66 to 68 | 134 to 135 | 66 to 68 | 0 to 0 | 0 |
|
||||
| decay1 | 45% | 6 | 108 to 112 | 217 to 229 | 108 to 112 | 0 to 0 | 0 |
|
||||
| decay6 | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
|
||||
| decay6 | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| decay6 | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| hyst | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
|
||||
| hyst | 34% | 6 | 59 to 61 | 120 | 59 to 61 | 0 to 0 | 0 |
|
||||
| hyst | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| twotier | 30% | 6 | 0 | 0 to 40 | 1 to 8 | 0 to 0 | 0 |
|
||||
| twotier | 34% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
| twotier | 45% | 6 | never | 714 to 722 | 360 | 0 to 0 | 0 |
|
||||
|
||||
|
||||
Q2. Acquired keys that sign, stay silent or leave, under v3 + leave 1 h (30 days)
|
||||
|
||||
| bought weight | bought keys | attacker's peak share of the denominator | share at the end | holds the veto (1/3) | stalled checkpoints | conflicting locks |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 40% | sign | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 0 | 0 |
|
||||
| 40% | silent | 39.8 to 39.8% | 30.0 to 30.0% | day 1 to 19 | 86402 to 86491 | 0 |
|
||||
| 40% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 119 to 144 | 0 |
|
||||
| 49% | sign | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 0 | 0 |
|
||||
| 49% | silent | 48.5 to 48.6% | 30.0 to 30.0% | day 1 to 23 to 24 | 86371 to 86408 | 0 |
|
||||
| 49% | leave | 30.0 to 30.0% | 30.0 to 30.0% | never | 143 to 188 | 0 |
|
||||
|
||||
Under v3 the silent bought 40 percent stalls every checkpoint of the 30 days (86,402 to 86,491), where `sim/results_v2.md` K at v2 measured 63,307 to 68,716: the frozen table keeps the bought weight in the denominator after the sliding table has aged it out, so a silent buyer pauses finality for a window, not until day 19 to 20. The leaving buyer holds 30.0 percent at most (its own hash), never the veto.
|
||||
|
||||
### 5.4 Reading
|
||||
|
||||
What holds. Every candidate keeps 0 conflicting final locks in every honest partition under two thirds (50/50, 40/40/20, 60/40, 70/30 for 360 minutes) except the fast decay, and every candidate conflicts at the 34 percent equivocator (139 to 612 locks in 360 minutes, the one-third bound of 3.11.2, unchanged). The leave rule's rows equal v3's in every partition, eclipse and equivocator case (no key leaves in those scenarios, which is the point: a partition does not sign leaves) and it is the only candidate that both ends tonight's pause in under an hour (0.04 days at 34, 45 and 50 percent: the one-hour delay) and keeps 0 conflicts in the 12-day splits.
|
||||
|
||||
What breaks. The fast decay (T 1 h, r 0.5/h) conflicts in EVERY 360-minute partition, including 50/50 honest with no attacker (467 to 473 conflicting locks, both sides locking alone at minute 120 to 123, exactly T + 1/(2r) = 2 h) and the poisoned eclipse at 2 and 4 hours (17 to 264 conflicts, the eclipsed side locking the attacker's fork after 109 to 111 minutes); it also fails the 12-day splits in 2 hours (34,146 to 34,254 conflicts). The slow decay (T 6 h, r 1/24 per h) passes every 360-minute row because the decay has not started, then both sides of the 50/50 split lock alone at day 0.75 to 0.78 and the 60/40 at 0.52 and 0.93 (31,545 to 32,246 conflicts in 12 days): the hazard moved to the day scale, not removed. The hysteresis floor keeps the honest splits clean (the 60 side of 60/40 locks alone at minute 58 to 61, 0 conflicts) and reopens the equivocator bound: 20 percent across a 50/50 split gives 565 to 597 conflicting locks from minute 60 (the 13.3 percent bound of 3 October is back after H hours), and it does nothing for tonight's 45 percent departure (the stayers' 55 percent is under its 56.7 percent floor: 30.00 days, the same as v3). The two-tier's provisional tier conflicts in every partition and eclipse (516 to 1,062 provisional locks per 360 minutes, 19 to 385 in the eclipses, 34,000 in 12 days) while its final tier equals v3; it is a report of "the connected majority agrees", never a lock.
|
||||
|
||||
The pass line (0 conflicting final locks in every scenario AND tonight's pause under an hour) is met by one candidate: the departure announcement. v2 would have met the hour on the devnet (35 minutes measured, 31 to 32 simulated at 45 percent) and not on mainnet (7.85 to 8.03 days); v3 meets neither (30.00 days, 120 devnet minutes, the frozen table's expiry, as the live chain is showing at the time of writing: 89.2 percent of the sliding table signing at 19:50Z and no lock).
|
||||
|
||||
### 5.5 The aggregation path tonight (the coordinator's question of 19:3xZ)
|
||||
|
||||
Measured: the 8 VRF-picked aggregators are drawn by weight (spec S1, fin-fixes); the fallback (any node, 15 DAA after the determination, aggregator `00000000`) produced 64 of the 251 certificates stored between 16:30 and 19:00Z; locks 6824 to 6842 formed while node 1 and the observer were down; the observer's node received 75 then 79 of 93 voters' votes through its 3 peers during the pause. The star the fleet forms is around the seed (`docs/bench-log.md`, the finality route: "on a Vast box the seed is the only peer"), not the Mac; if the seed fell, a one-peer box would lose blocks as well as votes, and the right fix is a peer floor (at least 4 outbound peers from the address book before a node reports synced), which is lane 5's bandwidth and p2p lane. Model of P(certificate | hub down) under the rule as written: with the fallback, a certificate forms whenever any node connected to two thirds of the signing weight exists, so the probability is 1 for any topology in which votes reach any node; without the fallback and with a star through the hub it is 0 for 8 aggregators or 800. The rule is already the right one; the harness case to add is `tools/finality-attacks` s4's shape (the eclipse) with the hub cut instead of a pool: N boxes peered to the seed and the hub, the hub killed for 300 s, pass line a lock within 2 checkpoints of the cut while two thirds of weight stays connected through the seed, and 0 conflicting certificates at the hub's return. Vote bytes per checkpoint are in 3.3 (26 KB at 93 voters, 2.3 MB at 8,192 single, about 1.2 KB aggregated).
|
||||
|
||||
## 6. Ranked proposals
|
||||
|
||||
| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | The departure announcement (candidate iv): a `leave` item (key, DAA score, signature) carried in blocks; D = 1 h after inclusion the key is in no denominator (sliding and frozen) and its votes are invalid; the fleet and app send it on a clean stop | tonight's pause (3.1): 42.7 percent left in three minutes and the frozen table held finality for a window; sim T: first lock 1 h after the departure at 34, 45 and 50 percent, 0 conflicts in every P row, Q2: leaving bought keys gains the attacker nothing | section 4.2 arithmetic; `finality_horizon.py` `leave` | 6 (spec text 3.1 W7 and 3.3; node: the item, its carriage, `voters_at` and `frozen_table` exclusion, unit test; app and fleet library: send on stop; fast-time harness case) | home miner, rig: the app sends the leave on Stop, so a clean exit never holds the network; a crash still ages out over 30 days (v3) unless the operator sends the leave on return, which the app offers; pool: one leave per server on maintenance; holder: fewer and shorter pauses; rollup customer: the same; node operator: one more item type | harness: 45 percent of weight stops with leaves, first lock within D + 1 checkpoint, 0 conflicts in the 50/50 and 60/40 splits and the 34 percent eclipse; sim T and P rows reproduced on the node |
|
||||
| 2 | Operational rule, no protocol change: a standing box never leaves the live chain for an experiment, and any orchestrated departure over 10 percent of weight is staged in slices under 10 percent an hour | the rehearsal took 36.5 percent at once; sim L2 and M4: a gradual departure costs nothing (every lock re-freezes the table) | spec 3.7 item 2 | 1 (the fleet library refuses to swap a standing box's chain; a `--slice` on the rehearsal script) | fleet operator: the swap takes longer; everyone else: no pause | the next rehearsal: `finality_active` stays true throughout |
|
||||
| 3 | Aggregate-first vote verification and aggregated in-block carriage as the mainnet default (spec 3.4.2 item 2, decided) | 4.3: per-vote verification is 1.6 s per checkpoint per node at 1,000 voters and 13 s at 8,192 (approximate); the crate already has `aggregate` and `fast_aggregate_verify` | 4.3 table | 8 (node: batch the votes for one (index, hash) and verify once, bisect on failure; the in-block aggregate item; measure on the fast-time harness at 1,000 synthetic keys) | home miner, rig, pool: a node that stays under one core at 1,000 voters; node operator: the same; holder: lock delay flat at 2 to 3 s to 8,192 voters | fast-time harness with 1,000 and 8,000 synthetic voters: lock delay p50 under 3 s, CPU under 25 percent of one core, 0 conflicts |
|
||||
| 4 | Report the two-tier state (candidate ii) as `finality_provisional` beside `finality_active`, never as a lock | sim P: 516 to 1,062 provisional conflicts per 360-minute partition, 19 to 385 per eclipse, about 34,000 in 12 days, final 0; sim T: provisional 0 to 0.2 devnet minutes after tonight's departure | 4.1 | 3 (node RPC field, explorer and wallet copy; spec 3.9 row) | exchanges: a third row in the guidance table ("provisional: proof of work plus a majority of the connected weight; credit nothing on it"); a holder sees why the pause is a pause | the explorer shows the field through a forced pause on the devnet; the guidance text reviewed by an operator (O-3.13) |
|
||||
| 5 | The ZK light client's circuit as a phase-two design doc with a cycle measurement | 4.5: 15 to 35 M cycles per step, approximate; the pairing is the term to measure | `lightclient_cost.py` | 10 (an SP1 guest that verifies one certificate at 93 and 1,000 voters with the Fp2 precompiles; cycle count on the 5090 and a 12 GB card) | phone, bridge, rollup customer: the per-year columns of 4.5 become one proof; prover tiers: a steady 30-s job | measured cycles within 2x of the estimate; proof per checkpoint under 30 s on a proving-only 5090 |
|
||||
| 6 | Hub-cut harness case for the aggregation path | 5.5: the fallback carried 26 percent of tonight's certificates; the seed, not the Mac, is the fleet's star | 5.5 | 3 | fleet operator: a proven answer to tonight's question | the case passes as written in 5.5 |
|
||||
| 7 | Do NOT adopt the decaying denominator (i) or the hysteresis floor (iii) | sim P1 and P3 (5.2): decay1 467 to 473 conflicting locks in a 360-minute 50/50 honest split and 34,146 to 34,254 in 12 days; decay6 31,545 to 32,246 in 12 days; hyst 565 to 597 at a 20 percent equivocator from minute 60 | 4.2 | 0 | a holder keeps the one-third bound in every view | none: a negative result |
|
||||
| 8 | Do NOT make prover attestations a lock condition before the coverage gate | 4.6: coverage 2.4 to 4.7 percent tonight, lag p99 62 s | 4.6 table | 0 now; 12 after the gate | holder: no new pause source; rollup customer: nothing lost, the four-state reading exists | 99 percent of blocks proven within 60 s for 7 days, 3 provers per block |
|
||||
|
||||
**1. The departure announcement.** Tonight's cost was a window-long pause caused by keys that left on purpose, under a job that knew it was taking them. The frozen table (F21) exists so that a side of a partition cannot fill its own table, and it does that; its price is that it cannot tell a departure from a partition. A signed leave is the one thing a departing key can give that a partitioned key cannot: it is seen, not inferred. Section 4.2 shows the one-third bound survives it in both halves of a split, the sim shows 0 conflicting locks in every partition, eclipse and equivocator row under it and a first lock one hour after a 34, 45 or 50 percent departure where v3 waits 30 days, and Q2 shows an attacker who buys keys to leave them is worse off than one who signs with them. The hour is a parameter: it must exceed the certificate relay plus one presence of the leave in blocks (minutes), and shorter is better for the operator; one hour matches the merge-depth bound and gives a key that leaves by mistake time to see it. What it does not cover: a crash, a power cut, a region going dark, which still age out as today; the app's Stop button and the fleet library send the leave, and a node that restarts after an unplanned outage can send it on return to shorten the pause from that point.
|
||||
|
||||
**2. Staged departures.** Every lock re-freezes the table, so weight that leaves while locks continue ages out of the frozen table as it ages out of the sliding one (M4's "gradual departure costs nothing"). Ten percent an hour keeps the stayers over two thirds at every step for any total departure under a third per three hours. This is a fleet-library rule, one afternoon, and it would have kept tonight's finality on without any protocol change; proposal 1 covers the case where staging is not possible.
|
||||
|
||||
**3. Aggregate-first verification.** The delay data of 3.2 is flat to 93 voters because the devnet's cost is the poll and the pump, not the pairings; the arithmetic of 4.3 says the pairings take over near 1,000 voters and break the 30-s cadence near 8,192. The crate the fork already uses has both halves of the fix; what is missing is the batching in `ingest` and the in-block aggregate item that 3.4.2 item 2 proposes. The gate is a synthetic-voter harness, which `tools/finality-attacks` can host (its `lib/net.mjs` starts nodes; a vmine with 1,000 keys is a flag away).
|
||||
|
||||
**4. The two-tier report.** The provisional tier is the active-denominator rule the project rejected on 3 October, and the sim says again why: it conflicts in every partition. As a reported state it is useful to a holder who wants to know whether the pause is a silent third (provisional true: the connected majority still agrees) or a split (provisional conflicting on the two sides), and useless to an exchange, which must credit nothing on it. Three hours, mostly copy.
|
||||
|
||||
**5 and 6** are measurements with a design attached, priced above. **7 and 8** are the negatives this lane is confident about: a denominator that shrinks on silence is the 3 October rule under another name, and a lock that waits for proofs is a lock that pauses whenever the proving market is thin, which it is.
|
||||
|
||||
## 7. Open questions and what could not be run
|
||||
|
||||
- The fleet wrote no lock-delay or voter-count row by 19:45Z (`block-rate-devnet2.md` is a template); the 93-voter figures here are node 1's own log. When RUN_A and RUN_B land, the 10 blocks/s row should be checked against 4.3's claim that the hop term, not the voter term, sets the delay.
|
||||
- The leave item does not exist in the node; the harness case of proposal 1 is designed, not run. Its interaction with W5 succession (O-3.11) and with a key that leaves and keeps mining (its blocks earn nothing, as the spec says for a succeeded key) needs the spec text.
|
||||
- BLS costs are approximate (crate benchmarks from memory, anchored by one measured JavaScript run). A `cargo bench` of `fast_aggregate_verify` at 93, 1,000 and 8,192 keys on the Mac is one hour and belongs with proposal 3.
|
||||
- The ZK light client's pairing cost in SP1 6.8.1 is the number the whole of 4.5 turns on; it is approximate until the guest of proposal 5 is counted.
|
||||
- The pause's end was not observed at the time of writing (expected about 20:40Z at the frozen table's expiry, or earlier if the departed boxes return); the observer rows will show which.
|
||||
- The box ran the sims at nice 19 under other agents' load; the tables are counts and days, not timings, so the load does not touch them.
|
||||
|
||||
## 8. Summary for the coordinator
|
||||
|
||||
Tonight's finality pause (first unlocked checkpoint 6843 at about 18:40Z, still paused at 19:30Z) is the two-thirds rule doing what it says, then the frozen table doing what F21 asked: 20 keys holding 42.7 percent of the frozen table left the live chain, the stayers held 53.1 percent at the first unlocked checkpoint and 74.9 percent of the sliding table from 19:14:53Z, and only Q5 explains why 74.9 percent did not lock; certificates formed through the hub outage and the aggregator fallback carried a quarter of them, so the topology hypothesis is refuted. The three findings: (1) under v2 the pause would have ended at 19:14:53Z, 35 minutes in, and on mainnet the same event is 7.7 days (v2) or 30 days (v3); (2) of the four candidate rules only the departure announcement keeps the one-third bound (0 conflicts in every partition row, first lock one hour after the departure), while decay and hysteresis reopen the double lock and the two-tier is a report; (3) renting a veto costs USD 8,424 x N x 0.52 for 30 days (USD 4,300 per GH/s of network), locking alone 2.03 x N for 30 days, and bought keys cost the same and decay in 30 days.
|
||||
675
docs/analysis/horizon/frontier.md
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
# Horizon lane 7: frontier. Predictions to 2030, what no proof-of-work chain has shipped, and what Igneum can
|
||||
|
||||
6 October 2026, evening UK. Lane 7 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon`, at origin/master 3f4f719). the project lead's words: "research anything else that we can research too, predictions, forward thinking, what we can actually do that has not been done or applied, think outside the box", and "be revolutionary". Main's bar: not features, but ideas that change what a proof-of-work chain is or what a GPU owner is to the world, each with its evidence, cost, gate, and the attack a Monero or Kaspa core developer would mount. I argue each attack as the project's own four personas would hear it (`.claude/agents/cryptographer.md`, `consensus-engineer.md`, `execution-engineer.md`, `miner-community-lead.md`).
|
||||
|
||||
Nothing in this file is a prediction of the coin's price, an offer to sell anything, or a change to any consensus parameter. Every chip figure is arithmetic on cited memory and logic figures; every GPU figure names its bench entry; a figure from memory says approximate. No em dashes.
|
||||
|
||||
**Read for grounding:** `docs/spec/00-overview.md`, `05-fees-and-economics.md`, `07-execution.md`, `09-pool-protocol.md`, `10-light-client.md`; `site/litepaper.html` (whole page, including "What Igneum does not claim"); `docs/fud-ledger.md` sections 3 (P1 to P10 present in the file; P11 to P23 are cross-referenced from the spec and round-3 entries), 4 (E1 to E8), 6 (C1 to C12), 9 (D1 to D6); `docs/commercial/prover-customer-brief.md`; `docs/design/payment-routes.md`, `developer-adoption.md`, `execution-layer.md`; `docs/analysis/chip-model-v3.md` sections 5 to 6; `docs/plans/counter-asic-3-status.md` section 4; `docs/analysis/prover-tiers-real-cards.md` (eleven rented cards, 6 October); `docs/analysis/economy-2026-10-04.md`; `docs/analysis/security-budget.md`; `docs/bench-log.md` line 2582 (rental cost of hash, measured 6 October); `vendor/rusty-kaspa/consensus/core/src/config/params.rs` (main checkout). `block-rate-devnet2.md` was still a template at 22:00 UK (RUN_A and RUN_B empty); nothing here depends on it.
|
||||
|
||||
**Model:** `sim/horizon/frontier/frontier_model.py` (pure Python, no numpy, about 50 ms; `python3 sim/horizon/frontier/frontier_model.py > sim/horizon/frontier/out.md`). Every table below marked "model" is printed by it; its `INPUTS` block labels each input measured, cited, designed or approximate with the source. Not run under the lock: it is arithmetic, not a measurement.
|
||||
|
||||
---
|
||||
|
||||
## 0. Everything ranked by payoff over difficulty
|
||||
|
||||
Payoff 1 to 5 is what the idea does for the chain's security, the coin's utility or the GPU owner's position in the world, if it works. Difficulty is Claude-side hours to a measurable prototype (the project lead's rule: hours, never weeks). Verdicts: do now, prototype, watch, never. The "never" rows carry a sharp reason so the rest are not fantasy.
|
||||
|
||||
| Rank | Idea | Payoff | Hours | Verdict | One line why |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 3.3 The weight table carried inside the recursive segment proof: a consensus proof at mergeset cost, so a browser verifies finality from one proof and asks no node for the voter set | 5 | 60 | do now (design and guest prototype) | Phase two's hardest item becomes incremental: each segment proof updates W2 by its own blue blocks; the cost is one BLS aggregate verify per 30 s inside the zkVM, which SP1 has precompiles for (approximate) |
|
||||
| 2 | 3.2 Work-stake: vote weight as the external-job bond | 5 | 24 | prototype | A bond nobody can buy: 30 days of blocks. The at-risk pool income is thousands of IGN against a 0.0015 IGN coin bond (model section 3); the design stays "no stake" because weight is work, not coins |
|
||||
| 3 | 3.7 Reproducible-build attestations in a registry contract; Ember refuses a release under N of M attestations | 4 | 12 | do now | Bitcoin's guix.sigs with the chain as the sigs repo; closes the devnet's "release key acts as operator" sentence (litepaper, Governance) |
|
||||
| 4 | 3.4 A WebAssembly verifier of the wrapped block proof in the tab | 4 | 16 | do now | Three working precedents (a16z Helios WASM, ProjectZKM ziren-wasm-verifier, xycloo wasm-groth16-verifier); the certificate half already runs at 58 to 155 ms (bench-log) |
|
||||
| 5 | 3.8 Ember as node, wallet and light client for everyone: node count equals miner count | 4 | 20 | do now | 1,000 testnet miners become 1,000 verifying nodes; Monero shows about 5,000 peers in 72 h (monero.fail), Ethereum 8,136 execution nodes (ethernodes); Sybil counts are irrelevant here because nothing counts nodes |
|
||||
| 6 | 3.15 A 30-s randomness beacon from the checkpoint VDF | 4 | 24 | prototype | The pipeline exists (10-min VDF, 4.47 ms verify); a drand-class beacon with no league and 120x drand's latency; honest limit is the class-group ASIC (Chia timelords) |
|
||||
| 7 | 3.1 The reward rule that prices rented hash out (pay per block falls when hash arrives faster than the 30-day weight) | 4 | 40 | prototype | Doubles the renter's break-even price (model section 2) and routes the cut to the proving pool, not to incumbents; the cost is a 30-day income ramp for honest newcomers, which the vote already imposes |
|
||||
| 8 | 3.5 Continuous miner-voted parameters, bounded per block like Ethereum's gas limit, for `B_p`, the floors and the window | 3 | 30 | prototype | Replaces two-week 60 percent proposals with a drift anyone can read on the chain; Kaspa's Crescendo was a fixed DAA score (`params.rs:648`), Bitcoin's BIP9 a 95 percent tally; neither moves a number continuously |
|
||||
| 9 | 3.16 The hourly program swap as a research dataset and the fleet library as a product | 3 | 10 | do now | 8,760 random kernels a year, compiled on three vendors with per-variant timings; compiler and GPU-architecture researchers have no such corpus; income small, standing large |
|
||||
| 10 | 3.13 Igneum as the settlement layer for GPU rental | 3 | 40 | prototype (escrow plus sampled verification) | The chain's fee is 3 to 5 orders under a 7 to 15 percent platform take (model section 5), but it can settle only what it can verify; the verifiable subsets are named |
|
||||
| 11 | 3.6 Treasury-less audit funding: burn-redirect bounties by 60 percent signal, review escrow on upgrade proposals | 3 | 20 | watch | The money exists only when the chain is used (USD 1,600 to 16,000 per 30 days at launch traffic, model section 4), and it is the switch spec 5.5 removed, with a veto |
|
||||
| 12 | 3.14 Proofs sold to AI labs for verifiable inference | 2 | 40 | watch | zkLLM: 803 s of proving per forward pass on LLaMA-2-13B (arXiv 2609.27367 citing 2404.16109); the competitor is a USD 0 TEE attestation on H100-class cards the fleet does not own |
|
||||
| 13 | 3.9 Hardware wallets that verify proofs | 2 | 12 | never on the secure element; do the companion verify | A bn254 pairing on a Cortex-M-class secure element is seconds to minutes (approximate); Ledger and Trezor do their heavy work in the companion app, and Igneum Wallet already verifies certificates with the node's code |
|
||||
| 14 | 3.12 The fleet as a public compute market (rendering, inference) priced in IGN | 2 | 60 | never for unverifiable work; do for the verifiable subsets | An escrow without a verifier is a trust-me payment with lower fees; Render uses result quorums, Akash reputation, io.net attestations (secondary), none of which a chain can check |
|
||||
| 15 | 3.11 Miners paid for proving others' chains as the main income, the lottery a tiebreaker | 1 | 0 | never by 2030; watch | All of Ethereum L1's proving at the Sep 2026 tracker cost is USD 36 a day; Igneum's year-1 emission is USD 13,700 a day at 0.005 (model section 7); demand must grow 1,000x against a cost curve falling 3x to 30x a year |
|
||||
| 16 | 3.10 Proof-of-useful-work: the lottery hash partly a proof | 1 | 0 | never | Every coupling of leader election to proving re-opens Aleo (fastest prover wins); Ball et al. 2017 and Ofelimos 2022 show the sampleability conditions, and zkVM proving meets none of them |
|
||||
|
||||
Predictions (section 2) are not ranked; they are inputs. The three ideas a Monero or Kaspa core developer would not have thought of are 3.2, 3.3 and 4.3 (the hourly program as a hardware census), argued in section 4.
|
||||
|
||||
---
|
||||
|
||||
## 1. Method
|
||||
|
||||
Three kinds of work:
|
||||
|
||||
1. **Trend lines with arithmetic.** Each 2030 prediction has a cited anchor (a product page, a tracker, a standards body, a secondary analysis labelled as such) and a formula in the model script. Where the trend is from memory (consumer VRAM generations) the row says approximate.
|
||||
2. **Idea arithmetic.** For the five ideas whose value depends on numbers (the rental tax, work-stake, the burn bounty, rental settlement, the proving-income ceiling) the script prints the table and the document reads it. The attack costs use the measured rental entry (`docs/bench-log.md` line 2582: 1,748 MH/s for USD 20.44 an hour, USD 0.0117 per MH/s-hour, about USD 11.69 per GH/s-hour) and are given per GH/s and evaluated at 1, 10, 100 GH/s and 1 TH/s, as the brief asks.
|
||||
3. **Prior art.** WebSearch on 6 October 2026 for every idea; the paper or repository that tried it is cited, or the idea is marked "new" when none was found. Claims about other chains cite the repository file when a clone exists in the main checkout (`vendor/rusty-kaspa`) and are marked "not cloned, approximate" otherwise.
|
||||
|
||||
No simulator in `sim/` was re-run and no node harness was started: every idea here is a design question whose first gate is a measurement named in its section, and tonight the fleet, PC 1, PC 2 and the devnet were in use for the class v4 rehearsal and the Devnet 2 block-rate runs. What I could not run is listed in section 6.
|
||||
|
||||
---
|
||||
|
||||
## 2. Predictions to 2030
|
||||
|
||||
Each row: the trend, its anchor, the arithmetic, and what it does to the chip model and the proving tiers. Tables marked model are `frontier_model.py` section 1.
|
||||
|
||||
### 2.1 GPU memory per card
|
||||
|
||||
| Year | Flagship consumer card | GB | Label |
|
||||
|---|---|---|---|
|
||||
| 2016 | GTX 1080 | 8 | approximate (from memory) |
|
||||
| 2018 | RTX 2080 Ti | 11 | approximate |
|
||||
| 2020 | RTX 3090 | 24 | approximate |
|
||||
| 2022 | RTX 4090 | 24 | approximate |
|
||||
| 2025 | RTX 5090 | 32 | cited (`chip-model-v3.md` 5.1: 16 x 2 GB GDDR7 on 512 bits) |
|
||||
|
||||
Compound growth 1.167 a year (4x in 9 years). Extrapolated: 51 GB in 2028, 69 GB in 2030 (model). The module arithmetic is sharper than the curve: a 512-bit board is 16 devices; Micron has ended 2 GB GDDR7 (TrendForce, 24 September 2026, via `chip-model-v3.md` 5.1) and 3 GB devices are USD 60 to 70, so the next flagship is 48 GB (16 x 3 GB) and 64 GB is the 2030 shape. The RTX 60 series on Rubin (GR20x) is reported for 2028 after two slips (kopite7kimi via videocardz.com and thepcenthusiast.com; rumour, not a product). Datacentre: HBM4 at 36 GB per 12-high stack, 288 GB per GPU on Rubin NVL72 (Wikipedia HBM page, Micron March 2026 production).
|
||||
|
||||
What it does to the chip model: nothing for the stored-dataset chip (f = 1), whose memory is already 24 to 32 GB against a dataset of 2 GiB growing to 4 GiB at year 4 (`chip-model-v3.md` 5.7: dataset size is "not a lever against this chip"). What it does for miners: the dataset schedule (2 GiB, doubling at years 4, 12, 28; litepaper) stays under every card from 8 GB for twelve years, and the proving side, not the mining side, is what wants VRAM (section 2.6).
|
||||
|
||||
### 2.2 Memory dollars per GB
|
||||
|
||||
| Point | USD per GB | Source |
|
||||
|---|---|---|
|
||||
| 2023, GDDR6 | 3.38 | Tom's Hardware, "GDDR6 VRAM prices plummet", USD 27 per 8 GB |
|
||||
| 2025, GDDR6 | 2.50 | TechSpot, "AI is eating all the DRAM" (2026) |
|
||||
| 2026, GDDR6 | 3.30 | TechSpot, same |
|
||||
| Sep 2026, GDDR7 2 GB device | 10.00 | TrendForce via `chip-model-v3.md` 5.1 |
|
||||
| Sep 2026, GDDR7 3 GB device | 21.67 | TrendForce (USD 60 to 70 per device) |
|
||||
| Oct 2026, HBM3E 36 GB stack | about 8.3 | siliconanalysts.com/data/hbm-pricing (factory gate; contract about 2x), approximate |
|
||||
| Oct 2026, HBM4 36 GB stack | about 15.3 | siliconanalysts.com (USD 550 per stack); Samsung quoting USD 4.50 to 4.90 per Gb for HBM4 against 1.50 for HBM3E (BigGo Finance), approximate |
|
||||
|
||||
GB per dollar fell in 2026 for the first time in a decade and DRAM supply is forecast tight through 2027 with new fabs in 2028 (SoftwareSeni "HBM4 delays and GDDR7 shortages"). Memory is reported at 70 to 80 percent of the bill of materials of high-VRAM consumer cards by late 2025 (BuySellRam, secondary).
|
||||
|
||||
What it does to the chip model: the f = 1 chip and the GPU buy the same devices, so the ratio of their memory bills is fixed; what moves is the share of each bill that is memory. The chip's bill is about 70 percent memory (USD 320 of USD 470, `chip-model-v3.md` 5.4) and the 5090's about 16 percent at MSRP (USD 320 of USD 1,999) or 9 percent at the 2026 street price of USD 3,695 (localaimaster.com). A doubling of device prices raises the chip's cost 1.7x and the card's 1.1x to 1.2x: **the stored-dataset chip gets dearer relative to the GPU through 2027**, and the dollars-per-MH/s row (USD 2.8 against 14.7 at MSRP, 5.4 against 27 at street prices) narrows a little and no more. The per-joule row does not move at all, and per joule is where the chip wins (section 2.3).
|
||||
|
||||
### 2.3 Random-read bandwidth: GDDR7, HBM3E, HBM4
|
||||
|
||||
The lottery is latency-bound: one hash advances one dependent 4-byte read per memory latency, so the number that matters is random reads per second per watt, not GB/s (`chip-model-v3.md` 5.3 and 5.5). That ceiling is set by bank count and activate windows (tRC, tFAW), not by pin speed, so 48 Gbps GDDR7 is 28 Gbps GDDR7 here, and HBM3E is HBM3.
|
||||
|
||||
| Memory system | Reads/s ceiling | Why | Label |
|
||||
|---|---|---|---|
|
||||
| GDDR7, 16 devices, 512-bit (the 5090 board) | 21.3 G | 4 activates per 12 ns per channel x 64 channels | approximate (`chip-model-v3.md` 5.3) |
|
||||
| RTX 5090 measured | 17.5 G | 82 percent of the ceiling | measured (bench-log Counter ASIC 2.0) |
|
||||
| HBM3 or HBM3E, one stack | 10.7 G | 16 channels | approximate |
|
||||
| **HBM4, one stack** | **21.4 G** | JEDEC JESD270-4 raises channels per stack from 16 to 32, each with two pseudo-channels (allaboutcircuits.com, EDN), which doubles activate parallelism if tFAW per channel holds | approximate, derived (model 1.3) |
|
||||
| A 48 GB GDDR7 board | 21.3 G | capacity does not add channels | approximate |
|
||||
|
||||
The f = 1 chip in 2028 on HBM4 (model 1.4; every figure arithmetic, approximate):
|
||||
|
||||
| Chip | MH/s per chip | W bare / with a 150 W shadow core at k = 1 | uJ per hash bare / shadow | Gain per joule vs the 5090 bare (2.40 uJ) | Gain under the class v4 shadow (card 2.95 uJ at N = 100,000) |
|
||||
|---|---|---|---|---|---|
|
||||
| GDDR7 f = 1 (today's row) | 166 | 78 / 228 | 0.47 / 1.37 | 5.1x | 2.2x |
|
||||
| HBM3 one stack | 84 | 27 / 177 | 0.32 / 2.12 | 7.5x | 1.4x |
|
||||
| HBM4 one stack (2028) | 167 | 36 / 186 | 0.22 / 1.11 | 11.0x | 2.6x |
|
||||
|
||||
**Prediction:** HBM4 doubles the stored-dataset chip's rate per stack at about the same watts, so its bare per-joule edge rises from about 7x to about 11x, and under the class v4 shadow from about 2.3x to about 2.7x. The lever that answers it is N, the program work in the latency shadow: at N = 200,000 the HBM4 chip reads 1.74x at k = 1, at N = 330,000 (the 5090's full ALU budget) 1.33x (model 1.4). The verifier cost is N x 32 ops per warp: about 1 ms at N = 100,000 on one M5 Max core (measured class, counter-asic-3-status item 8), about 3 ms at 330,000, inside the 10 ms gate; the 2019-class core is unmeasured (O-1.14).
|
||||
|
||||
**Consequence and proposal (for the coordinator, not a change tonight):** write the schedule for N into the era draw at genesis, the way the dataset size already is: a candidate is a doubling of N per era until the verifier gate binds (about 1,000,000 ops, 10x of headroom on the M5 Max). The memory generation it answers arrives every two to three years and the chain must not need a human release to answer it. Per tier: no hash-rate cost while cards stay latency-bound (the M5 Max binds at about 290,000, the 9070 XT at about 650,000, approximate), watts up toward TGP (a 5090 from 326 toward 575 W, which the Ember power cap already manages), a verifier cost nodes and pools pay in milliseconds.
|
||||
|
||||
### 2.4 Price per card
|
||||
|
||||
| Card | Launch MSRP | 2026 street | Source |
|
||||
|---|---|---|---|
|
||||
| RTX 5090 | USD 1,999 | USD 3,695 to over 5,000 | `chip-model-v3.md` 5.1; localaimaster.com; tech-insider.org ("RTX 5090 tops USD 5,000"), secondary |
|
||||
| RTX 4090 | USD 1,599 (approximate) | rental USD 0.28 to 0.60 an hour (gpus.io median) | rental cited, MSRP from memory |
|
||||
|
||||
Prediction: consumer card prices track memory prices through 2027 and ease in 2028 when fab capacity lands. For Igneum the price per card matters twice: the honest fleet's capital cost (not in the security budget, which is power only, `security-budget.md` section 6) and the renter's hourly price, which fell to USD 0.21 to 0.44 per 5090-hour on Vast.ai (getdeploying.com, 6 October 2026) even as purchase prices rose, because rented supply is sunk capital. **The rental market, not the purchase market, prices the 51 percent attack**, and the measured entry is USD 11.69 per GH/s-hour at RunPod list prices with the market unable to supply 20 more pods when asked (bench-log line 2582). At a TH/s: USD 11,700 an hour, and no supply.
|
||||
|
||||
### 2.5 The chip-fab cost curve
|
||||
|
||||
| Node | Mask set | Source | Igneum reading |
|
||||
|---|---|---|---|
|
||||
| 28 nm | USD 1 to 3 M | TubeTime (3 M); VBsemi (over 1 M) | The f = 1 memory-controller chip: no mixer on the die, a USD 5 to 30 M project (`asic-resistance-history.md` 2.5) |
|
||||
| 7 nm | USD 10 to 15 M | VBsemi; Hacker News thread | The f = 0 recompute chip with 256 MiB on die: USD 50 to 75 M |
|
||||
| 5 nm | USD 6.5 M (2026 data) to 30 M (2023 estimate) | siliconanalysts; HN | The shadow core (30 mm^2 at N5 for N = 100,000) drags the f = 1 chip toward this node, or to a reticle-class 28 nm die |
|
||||
| 3 nm | USD 15 to 22 M (Q4 2025), up to 40 M (older) | siliconanalysts; semianalysis | Not relevant to a chip whose cost is memory |
|
||||
|
||||
Prediction: mask cost at a fixed node falls (5 nm quoted at 30 M in 2023, 6.5 M in 2026) while the leading node rises. So the shadow lever's fab-bill teeth weaken about 4x over three years; what holds in 2030 is the rate and joule arithmetic of 2.3, not the bill. **The stored-dataset chip gets cheaper to design and dearer to populate** through 2027, and the net is roughly flat against the GPU on dollars; per joule it gains with each memory generation unless N grows with it.
|
||||
|
||||
### 2.6 zkVM proving speed per dollar
|
||||
|
||||
| Point | USD per Ethereum L1 block proof | Hardware | Source |
|
||||
|---|---|---|---|
|
||||
| Jan 2025 | 1.69 | about 160 RTX 4090s for 90 percent real-time, USD 300 to 400 K cluster | HackMD "Ethproofs 2025 review" (willcorcoran); Succinct SP1 Hypercube blog (May 2025), secondary |
|
||||
| Dec 2025 | under 0.04 | 16 x RTX 5090 (SP1 Hypercube: 99.7 percent of blocks under 12 s; cluster under USD 100 K); Pico Prism 16 GPUs (Brevis blog, Feb 2026) | same, secondary |
|
||||
| Apr 2026 | | Cysic Venus 7.4 s on 24 GPUs | bex.co, secondary |
|
||||
| Aug 2026 | | ZisK p99 9.62 s on 4 x RTX 5090 | GitHub comparative analysis (Ricosworks1), secondary |
|
||||
| Sep 2026 | about 0.005 | "sub-half-cent" fields on ethproofs | same, secondary |
|
||||
|
||||
The 20-month ratio is 338x, about 33x a year (model 1.6). That cannot continue: it is software catching up with hardware. The table below uses 1.5x, 3x and 10x a year from the shard times measured on eleven rented cards on 6 October (`prover-tiers-real-cards.md`, the v1 shard, 4.7 M cycles).
|
||||
|
||||
| Card | Beside the miner today, s | Alone today, s | 2028 at 1.5x a year (beside / alone) | 2028 at 3x | 2028 at 10x | Under 10 s beside the miner in 2028? |
|
||||
|---|---|---|---|---|---|---|
|
||||
| RTX 3060 12 GB | 37.5 | 14.4 | 16.7 / 6.4 | 4.2 / 1.6 | 0.4 / 0.1 | at 3x or more |
|
||||
| RTX 4060 8 GB (core-only beside) | 22.1 | 18.4 | 9.8 / 8.2 | 2.5 / 2.0 | 0.2 / 0.2 | even at 1.5x |
|
||||
| RTX 4070 12 GB | 27.3 | 12.1 | 12.1 / 5.4 | 3.0 / 1.3 | 0.3 / 0.1 | at 3x or more |
|
||||
| RTX 4060 Ti 16 GB | 34.6 | 11.6 | 15.4 / 5.2 | 3.8 / 1.3 | 0.3 / 0.1 | at 3x or more |
|
||||
| RTX 3080 10 GB | 25.6 | 7.1 | 11.4 / 3.2 | 2.8 / 0.8 | 0.3 / 0.1 | at 3x or more |
|
||||
| RTX 3090 24 GB | 19.9 | 14.9 | 8.8 / 6.6 | 2.2 / 1.7 | 0.2 / 0.1 | even at 1.5x |
|
||||
| RTX 4090 24 GB | 26.1 | 6.3 | 11.6 / 2.8 | 2.9 / 0.7 | 0.3 / 0.1 | at 3x or more |
|
||||
| RTX 5070 12 GB | 37.2 | 4.8 | 16.5 / 2.1 | 4.1 / 0.5 | 0.4 / 0.0 | at 3x or more |
|
||||
| RTX 5090 32 GB | 10.7 | 6.3 | 4.8 / 2.8 | 1.2 / 0.7 | 0.1 / 0.1 | even at 1.5x |
|
||||
|
||||
**Prediction:** at the floor rate (1.5x a year, which is the GPU hardware cadence alone) only the 24 GB and 32 GB cards mine and prove inside 10 s in 2028; at 3x a year (half the historical software rate) every card from the 3060 up does, and an 8 GB card alone proves in 2 s. **The block-proof target ("under 10 s as provers improve", CLAUDE.md) should be written as a function of the measured fleet median shard time, re-read each era, not as a date.** Per tier: a 12 GB desktop card is the swing tier; under 1.5x it proves alone in under 7 s but not beside its miner, so the hand-off profile (`prover-tiers-real-cards.md`) is the thing to ship, not a bigger card.
|
||||
|
||||
What the cost curve does to the economics: the dollars per proof on the open market fall as fast as the volume rises, which is the arithmetic behind the "never by 2030" of 3.11.
|
||||
|
||||
---
|
||||
|
||||
## 3. The ideas, one section each
|
||||
|
||||
Each section: the idea in a paragraph; why nobody shipped it (cited, or "new"); what Igneum already has; the model; hours; the gate; the per-tier consequence; the Monero attack; the Kaspa attack; the verdict.
|
||||
|
||||
### 3.1 The reward rule that prices rented hash out
|
||||
|
||||
**The idea.** The pulse attack M14 (a renter arrives, mines for an hour, leaves) is recorded in the ledger and the finality rule already denies rented hash a vote. The reward side is untouched: a renter is paid per block like anyone. The rule: the block subsidy paid to a producer is multiplied by `m = clamp(W30 / H_now, 0.25, 1)`, where `W30` is the 30-day work-weighted hash the finality window already computes (blue blocks per DAA second over the W2 window) and `H_now` the DAA-window estimate. The remainder `(1 - m)` of the subsidy goes to that block's proving-pool escrow, not to incumbents and not to a burn. Fees are untouched. Hash that arrives faster than the 30-day weight can follow is paid less per block until the weight catches up.
|
||||
|
||||
**Why nobody shipped it.** Bitcoin Cash's EDA and every emergency rule since adjusted difficulty, never pay (approximate; the consensus engineer's list). Kaspa's DAA retargets per block over a sampled window (`vendor/rusty-kaspa/consensus/src/processes/difficulty.rs`, main checkout) and pays per block. Monero's RandomX changes what hash is, not what it is paid. Ethash coins and Ergo pay per block. No chain ties the subsidy to the ratio of fresh to sustained hash, because no chain had a sustained-hash number in consensus; Igneum has it in W2. Prior art searched: none found. New.
|
||||
|
||||
**What Igneum already has.** W2 (blue blocks per key over 30 days) and the DAA estimate are both in the node; the proving-pool escrow exists in execution state (spec 7.7 item 6); the emission split is a state transition by rule (design 1.1).
|
||||
|
||||
**The model** (frontier_model.py section 2, the measured rental entry USD 11.69 per GH/s-hour):
|
||||
|
||||
| Network hash | Attacker adds | H_now / W30 | m | Attacker IGN per hour, no rule | With rule | Rent USD per hour | Break-even IGN price, no rule | With rule |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 1 GH/s | 1 GH/s | 2.0 | 0.50 | 57,038 | 28,519 | 12 | 0.00020 | 0.00041 |
|
||||
| 10 GH/s | 10 GH/s | 2.0 | 0.50 | 57,038 | 28,519 | 117 | 0.00205 | 0.00410 |
|
||||
| 100 GH/s | 100 GH/s | 2.0 | 0.50 | 57,038 | 28,519 | 1,169 | 0.02049 | 0.04099 |
|
||||
| 1 TH/s | 1 TH/s | 2.0 | 0.50 | 57,038 | 28,519 | 11,690 | 0.20495 | 0.40990 |
|
||||
| 10 GH/s | 50 GH/s | 6.0 | 0.25 | 95,064 | 23,766 | 584 | 0.00615 | 0.02459 |
|
||||
|
||||
A renter who doubles the network needs twice the coin price to break even; one who sextuples it needs four times. The diverted subsidy (57,000 to 85,000 IGN an hour in these rows) reaches the provers of the same blocks, who are the sustained population by sortition weight (spec 7.2). The honest-growth cost: a listing that doubles honest hash overnight cuts every miner's subsidy per block in half on top of the halving difficulty already imposes, until W30 catches up (the 30-day ramp of ledger C7, 0.9x by day 28 to 31). The floor 0.25 bounds the worst case at 4x.
|
||||
|
||||
**Hours.** 40: the rule in the coinbase state transition (8), W30 as a consensus value from the window the finality module keeps (8), the economy simulator scenario f with and without the rule (8), the fast-time harness timestamp test (8), spec text and tests (8).
|
||||
|
||||
**The gate.** (a) `sim/economy/sim.py` scenario f (a pool with the network's own hash arriving on day 10): incumbents' income under the rule above the no-rule row for all 30 days, newcomers' below. (b) The fast-time harness with headers back-dated inside Kaspa's 132-s tolerance: `m` moves under 2 percent. (c) Scenario d (a 20 percent operator vanishes): `m` stays at 1 (hash fell, so H_now < W30 and nobody is cut).
|
||||
|
||||
**Per tier.**
|
||||
|
||||
| Tier | Consequence |
|
||||
|---|---|
|
||||
| Home miner, 8 to 32 GB | In a doubling month, 25 percent of the pre-event subsidy instead of 50; unchanged in a steady month; a newcomer earns half-rate for its first month, as it votes nothing for its first month already |
|
||||
| Rig | The same per block; a rig that joins during a listing spike earns half for a month |
|
||||
| Pool user | The same through PPLNS; the pool's dashboard should show `m` |
|
||||
| Prover | Gains: the diverted share lands in the pool escrow and is paid by sortition weight |
|
||||
| Holder | Emission schedule unchanged in total; a larger share of it reaches sustained keys during spikes |
|
||||
| Rollup customer | Nothing |
|
||||
| Node operator | One more consensus value (W30) and one multiplier in the coinbase rule |
|
||||
|
||||
**The Monero core developer's attack.** "You have built an incumbents' cartel. Every rule that pays old miners more than new miners entrenches whoever was there first; RandomX exists so that a newcomer with a laptop earns exactly what a veteran earns per hash. Your 'to the pool, not incumbents' is cosmetic: sortition is by weight, and weight is the incumbents. And your W30 is your own finality window, so a 30-day-old farm that goes dark and returns is 'sustained' while a thousand honest newcomers after a listing are taxed for a month. Qubic reached 23 to 34 percent of our hash for weeks in August 2025 (arXiv 2512.01437) by renting and by paying miners in its own token; your rule would have taxed the honest miners who moved to P2Pool to fight it, because they were new keys." Answer: the tax is per block not per key, so moving pools under the same key costs nothing (spec 9.6), and a returning farm's W30 is its own blocks, which it did not make while dark. The entrenchment point stands and is the cost the gate measures.
|
||||
|
||||
**The Kaspa core developer's attack.** "H_now is your DAA estimate and the DAA is manipulable by timestamps inside the tolerance; a producer can lower H_now for its own block by back-dating within 132 s and raise its own m. Second, W30 is a function of the DAG past and differs between two honest tips every second; a reward that depends on it makes two honest nodes disagree about the coinbase amount of the same block unless W30 is read at a fixed ancestor (the checkpoint), and then it lags. Third, you have made emission depend on a window of 2.6 million blocks: your pruning point must now keep that window's per-second counts, which Kaspa prunes." Answer: read both numbers at the block's selected parent's last certified checkpoint (deterministic, in every node's past), accept the 30-s lag, and the timestamp test is gate (b). The pruning point already keeps the W2 window for finality (spec 3), so no new retention.
|
||||
|
||||
**Verdict: prototype.** Doubles the renter's break-even and costs honest newcomers a month of half subsidy, which is the same month the vote already costs them; gate (a) decides whether miners will wear it.
|
||||
|
||||
### 3.2 Work-stake: vote weight as the external-job bond
|
||||
|
||||
**The idea.** The external job market needs a bond because a customer waits (spec 5.4, O-5.6: `maxPgas x f_p x 1.5` in IGN, slashed on a late or bad proof). Replace the coin bond with the key's 30-day vote weight: a key that claims an external job and delivers late or wrong loses a share `s` of its weight for 30 days, the way equivocation strips 100 percent (spec 3.6). Weight is blue blocks. It cannot be bought, borrowed or bridged; it can only be mined, in public, over 30 days. The job market then needs no IGN escrow from the prover, which removes the capital barrier that keeps home cards out of Boundless (ZKC collateral) and Succinct (PROVE staking, docs.succinct.xyz/docs/provers) while keeping a bond larger than either.
|
||||
|
||||
**Why nobody shipped it.** Every proving network bonds in its own token (Boundless: stake scales with aggregate proving work per epoch, docs.boundless.network/zkc/mining/overview; Succinct: stake required to bid, more stake more concurrent auctions). No proof-of-work chain had a non-transferable, slowly earned weight per key until Igneum's finality rule. Decred's tickets are bought with coins; Ethereum's slashing is coins. New.
|
||||
|
||||
**What Igneum already has.** W2 per key, the 30-day strip for equivocation, the sortition that already draws assignees by weight (spec 7.2), the job record format (design 6).
|
||||
|
||||
**The model** (frontier_model.py section 3):
|
||||
|
||||
| Key's hash share | Blocks per 30 days at 1 bps | 30-day pool income at risk, IGN | Lost at s = 25 percent | Lost at s = 100 percent | The coin bond for one 1 B-cycle job at the floor |
|
||||
|---|---|---|---|---|---|
|
||||
| 0.01 percent | 259 | 1,643 | 411 | 1,643 | 0.0015 IGN |
|
||||
| 0.1 percent | 2,592 | 16,427 | 4,107 | 16,427 | 0.0015 IGN |
|
||||
| 1 percent | 25,920 | 164,271 | 41,068 | 164,271 | 0.0015 IGN |
|
||||
| 10 percent | 259,200 | 1,642,706 | 410,676 | 1,642,706 | 0.0015 IGN |
|
||||
|
||||
The at-risk amount is five to nine orders of magnitude above the designed coin bond, before counting the lost vote. A late proof must be defined in DAA time against the claim (the P9 decision's 120-s claim timeout is the starting value), with one strike of grace per 30 days so a partition does not strip an honest key on its first miss.
|
||||
|
||||
**Hours.** 24: the strip rule in the finality module keyed by a job-fault record (8), the fault record in the job contract (design 6) with the evidence a node checks (8), tests and a devnet injection script (8).
|
||||
|
||||
**The gate.** Phase 4 devnet: 1,000 jobs with a 10 percent injected late or wrong rate: every injected fault stripped; zero honest keys stripped across a 60-s partition; a customer's job never waits more than the claim timeout plus one open window.
|
||||
|
||||
**Per tier.**
|
||||
|
||||
| Tier | Consequence |
|
||||
|---|---|
|
||||
| 8 GB solo miner below dust (under 100 blocks in 30 days) | No weight, so no external jobs under this rule; shards (no bond) unchanged; the pool protocol gives it a route (the pool's key, the pool's weight) |
|
||||
| 12 to 32 GB home miner above dust | Takes external jobs with no IGN locked; one bad job costs a quarter of a month's sortition income and a quarter of its vote for 30 days |
|
||||
| Rig | The same, at the rig's weight; the rig operator's whole weight backs each job, so a rig claims only jobs it can finish |
|
||||
| Pool user | The pool's weight is the bond; the member's share of pool income carries the pool's record |
|
||||
| Prover | A reputation nobody can buy, visible on chain per key |
|
||||
| Holder | No IGN is locked in bonds, so no bond capital sits idle |
|
||||
| Rollup customer | A bond measured in 30 days of public mining instead of a token balance; the customer brief's "your chain's own bond and slashing apply" becomes "Igneum's weight is at stake" once jobs settle on Igneum |
|
||||
| Node operator | One more strip condition in a module that already strips |
|
||||
|
||||
**The Monero core developer's attack.** "CLAUDE.md says no stake anywhere in consensus. You have just made the vote weight a stake: it is at risk for an execution-layer fault. Whatever you call it, a prover now rationally hedges by splitting its mining across two keys, one that votes and never proves, one that proves and holds dust weight, which your F17 says buys nothing for the vote but buys everything here: the proving key has nothing to lose. So the bond is only real for operators too small to split, which is backwards." Answer: the sortition draws assignees in proportion to weight (spec 7.2 step 2), so a dust key is drawn with probability near zero and the proving key must carry weight to be assigned at all; the hedge costs the prover its assignments. The attack is right that this is a stake of work; the design's "no stake" means no coin balance in consensus, and that still holds. The spec wording needs the distinction.
|
||||
|
||||
**The Kaspa core developer's attack.** "'Late' is not a fact on a DAG. A proof included in a block at DAA score D is late relative to the claim at D minus T only along a chain; a reorg moves D. You will strip a key on one chain and not on another, and the strip is a consensus input to finality. Also, the fault evidence rides in blocks, so a producer who dislikes a prover can withhold its proof for T seconds and then carry the fault record. That is a griefing vector you did not have when nothing waited on a prover (ledger P9)." Answer: the evidence rule must be relative to the carrying block's own chain (as proof records are, spec 7.2 item 5), the deadline must be long relative to merge depth, and the withholding vector is real: a proof gossips to every producer, so withholding needs a majority of producers for T seconds, and a strip is only applied if no block in the carrier's past carried the proof. The griefing cost is one window of a majority, the same bound the finality rule already lives with.
|
||||
|
||||
**Verdict: prototype.** The bond nobody can buy; the two attacks name the wording (work-stake is not coin-stake) and the rule (evidence relative to the carrier's chain) that the prototype must carry.
|
||||
|
||||
### 3.3 The weight table carried inside the recursive segment proof: finality attested by provers, a consensus proof at mergeset cost
|
||||
|
||||
**The idea.** Phase two's consensus proof is scoped as "a zkVM program over the 30-day header window and the vote certificates" (design 7; O-10.8), which is 2.6 million headers per proof and the reason it is phase two. But the segment proof already recurses: segment N verifies segment N minus 1 (spec 7.8 item 1, measured on the 5090). Carry the W2 weight table as a public commitment inside that recursion. Each segment's guest takes the previous segment's committed weight table, adds the blue blocks of its own mergeset per vote key hash (the segment is exactly the mergeset of its chain block, spec 7 terms), ages out the blocks that left the 30-day window, and commits the new table. Every 30 s, when a certificate exists for a checkpoint inside the segment, the guest verifies the BLS aggregate against the table it holds and emits "checkpoint i certified under rule v2 with x percent of total weight". The segment proof then attests both execution and finality, and a light client verifying one wrapped proof learns the certified checkpoint and the state root with no voter set fetched from any node. The provers are the attesters of finality, by construction, with no new role.
|
||||
|
||||
**Why nobody shipped it.** Ethereum's sync-committee light clients (Helios, a16zcrypto.com "Building Helios") trust a committee and fetch it; Succinct's eth-proof-of-consensus (github.com/succinctlabs/eth-proof-of-consensus) proves sync-committee signatures in a SNARK but over a fixed committee, not a weight table that moves with every block. No proof-of-work chain has a weight table to carry. Mina carries a recursive proof of the whole chain but its consensus is stake (approximate, not cloned). The incremental-weight-in-recursion form: new.
|
||||
|
||||
**What Igneum already has.** The aggregator guest with `chain_len` and `prev` (spec 7.8 item 1), the 340-byte `BlockOutput`, the canonical voter list and bitmap (spec 3.10 C3), SP1 with BLS12-381 precompiles (approximate: SP1's precompile set includes bls12-381 field operations; the pairing cost inside the guest is unmeasured), O-10.2 which already asks for a header commitment to the weight table.
|
||||
|
||||
**The model.** Cost per segment: the segment adds at most 180 blocks per chain block at 1 BPS (mergeset limit, spec 7.1) times `N = 8` chain blocks, so about 1,440 table updates (a hash-map add and an age-out) per segment, which is negligible beside the execution; plus one BLS aggregate verification per certificate, at most one per 30 s. The BLS verify is the cost: G1 aggregation of up to V keys and one pairing. In SP1 with the bls12-381 precompiles a pairing is of the order of tens of millions of cycles (approximate, from memory of the precompile benchmarks; unmeasured here), so at 1 pgas = 1,000 cycles it is tens of thousands of pgas, about one shard's budget (`S_p` 30,000 pgas) per 30 s. That is a real cost: about one extra shard per 30 blocks, 3 percent of proving capacity at launch traffic. The table commitment is 32 bytes in the public values; the voter list for a 10,000-key table is 10,000 x 60 bytes = 600 KB of witness per segment, which gossips with the shard witnesses (design 5.1: witnesses are not consensus data).
|
||||
|
||||
**Hours.** 60: the guest's table update and commitment (16), the BLS verify inside the guest and its cycle count on the 5090 (16, needs PC 2 or a fleet box), the light-client path that reads the certified index from the public values (8), the spec text for 7.8 and 10 (8), a fast-time run where a partition's two certificates are both presented to the guest and it accepts one (12).
|
||||
|
||||
**The gate.** (a) Cycle count of one certificate verification inside the guest under 50 M cycles on the pinned SP1 (so under two shards). (b) The browser card (spec 10.8) shows "voter set: verified" with no node asked for the set. (c) The fast-time C4 scenario (a certificate over a chain the node is not on, `docs/fud-ledger.md` C4 sweep): the proof refuses a certificate whose signers' weight at that block is under 2/3 of the table it carries.
|
||||
|
||||
**Per tier.**
|
||||
|
||||
| Tier | Consequence |
|
||||
|---|---|
|
||||
| Home miner, any card | Nothing changes in mining; a 12 GB prover's shard gets the certificate verification about once in 30 shards |
|
||||
| Rig, prover | About 3 percent more proving work at launch traffic, paid from the same pool; the aggregator's record grows by 32 bytes |
|
||||
| Pool user | Nothing |
|
||||
| Holder, wallet user | A phone or tab verifies "locked" from one proof and trusts no node for the voter set; the spec 10.1 row "voter list from nodes" is deleted |
|
||||
| Rollup customer | The bridge on Ethereum verifies one wrapped proof and needs no relayer or committee: this is the proof bridge of spec 7.3, delivered earlier |
|
||||
| Node operator | The witness gossip carries the voter list per segment (600 KB at 10,000 keys) |
|
||||
|
||||
**The Monero core developer's attack.** "You have moved finality's safety from a BLS signature every node checks to a SNARK every node trusts. A soundness bug in SP1 (ledger P7) now forges not only a state root, which full nodes veto by native execution, but a certificate, which full nodes cannot veto because they verify the real BLS certificate separately and will disagree with the proof. Which do you believe? And your 2/3 test inside the proof is against a table the proof itself computed; a bug in the table update is a bug in finality, and it ships in a guest program, not in node code anyone reads." Answer: full nodes keep verifying the BLS certificate natively and the native-execution veto extends to the public values (a record whose certified index or table commitment differs from the node's own is invalid, spec 7.2 item 5 as written), so a forged certificate is, as for state, a light-client problem and never a chain split. The table update is a second implementation of W2 and must be differential-tested against the node's (gate c).
|
||||
|
||||
**The Kaspa core developer's attack.** "The weight table is defined over the block's DAG past (W2 counts blue blocks in the past of the chain block); your segment is the mergeset of chain block C in GHOSTDAG order, so the incremental update is only correct if every block in the window is in exactly one segment's mergeset, which holds for blue and red blocks of the selected chain's mergesets, but a reorg of the selected chain re-cuts the segments and the table must be re-derived from the fork point. Your proof chain breaks at every reorg deeper than one segment, and at 10 BPS with k = 124 a reorg of 8 chain blocks is ordinary." Answer: correct, and the recursion already restarts at an unproven segment (spec 7.8 item 7, the unproven rule); a reorg deeper than a segment invalidates the records of the abandoned chain as it does today. The table commitment must therefore be part of the statement per chain block, re-proven on the new chain, which is what re-proving the segment already does. The cost at 10 BPS is the open question for the gate.
|
||||
|
||||
**Verdict: do now (design and guest prototype).** It turns phase two's hardest item into an incremental one on code that exists, and it is the only road to "your browser verifies Igneum" with no node in the trust row.
|
||||
|
||||
### 3.4 A WebAssembly verifier of the wrapped block proof in the browser
|
||||
|
||||
**The idea.** The homepage card verifies a BLS certificate in JavaScript today (58 to 68 ms warm, 139 to 155 ms cold, bench-log round 6, P3). Ship the other half: the Groth16 or Plonk wrapper of the segment proof verified in WebAssembly in the tab, with the measured millisecond count shown.
|
||||
|
||||
**Why nobody shipped it on a proof-of-work chain.** Because no proof-of-work chain proves its blocks. The working precedents are rollup-side: ProjectZKM's `ziren-wasm-verifier` (github.com/ProjectZKM/ziren-wasm-verifier: "Verify STARK, Groth16 and Plonk proofs in browser", one Rust codebase to native and WASM), xycloo's `wasm-groth16-verifier` (github.com/Xycloo/wasm-groth16-verifier, with a live demo), a16z's Helios shipped as `@a16z/helios` on npm with WASM bindings (github.com/a16z/helios issue 76 and the npm package).
|
||||
|
||||
**What Igneum already has.** `site/verify/core.js` (BLAKE2b header hashes, canonical voter list, BLS aggregate over `@noble/curves`), the SP1 light verifier as a 58 MB native binary (bench-log, "the program id split"), the `wrap` step in the `ProofSystem` trait (design 5.6) unbuilt.
|
||||
|
||||
**The model.** A Groth16 proof over bn254 is three group elements, about 128 bytes compressed (spec 10.5, approximate); verification is one multi-pairing. In WASM a bn254 pairing is of the order of 10 to 50 ms on a laptop core (approximate, from the ziren and xycloo demos' order of magnitude; unmeasured here). Bytes per day in phase two on-demand mode: 800 bytes per open (spec 10.5).
|
||||
|
||||
**Hours.** 16: wrap the pinned aggregator proof to Groth16 with SP1's wrapper on a 24 GB fleet card (8, the P3 phase 2 benchmark brought forward), compile the verifier to WASM and wire it to the card (8). The wrapper's own cost on consumer hardware is the open measurement R4.
|
||||
|
||||
**The gate.** The card shows the wrapped proof verified in the tab, with bytes and milliseconds, on a phone-sized viewport, against the live devnet; the number lands in the bench-log with the browser and the device.
|
||||
|
||||
**Per tier.** A home miner's Ember node serves the proof to the tab; a holder with no node verifies state in the tab (the state root still rests on a certificate the client is given until 3.3 lands); a rollup customer sees the verifier it will run on its own chain; node operators serve one more 128-byte object.
|
||||
|
||||
**The Monero core developer's attack.** "A verifier in a tab served by your domain verifies whatever your domain says the verifying key is. Your 'no middleman' is your web server. Monero's answer to this class is: run a node." Answer: correct, which is why spec 10.8 already removed "no node, no trust, no middleman" and why the phone app with a pinned seed list is the client that meets 10.6; the tab is a demonstration with its trust row stated on the card.
|
||||
|
||||
**The Kaspa core developer's attack.** "Fine, it verifies a proof. Of which chain? The proof commits to a chain block hash; the tab needs to know that block is on the selected chain at or below a certified checkpoint, which it asks a node for (spec 10.4 item 4). You verified the arithmetic and trusted the topology." Answer: correct until 3.3 folds the certified index into the same proof.
|
||||
|
||||
**Verdict: do now.** Cheap, precedented, and the phase 2 wrapper measurement has to happen anyway.
|
||||
|
||||
### 3.5 Continuous miner-voted parameters, bounded per block, in place of two-week proposals
|
||||
|
||||
**The idea.** Spec 5.8 sets a parameter the genesis rules leave to miners by a registered proposal passing 60 percent of blue blocks over two weeks. For the handful of parameters that are dials rather than switches (`B_p`, `S_p`, the base-fee floors, the exclusive window, the external claim timeout) use Ethereum's gas-limit mechanism instead: each block carries the producer's vote for each dial, the value in force at a block is the median of the window's votes, and the median may move at most 1/1,024 of its value per block, within a hard range fixed at genesis. No proposal, no bit, no two-week window, no human. Switches (a new instruction family, a proof-system version) keep the 90 percent signal.
|
||||
|
||||
**Why nobody shipped it this way.** Ethereum moves its gas limit by producer vote, bounded to 1/1,024 of the parent's limit per block (geth `core/block_validator.go`, VerifyGaslimit; approximate, not cloned). Bitcoin's BIP9 is a 95 percent tally over 2,016 blocks with LOCKED_IN and one more retarget before activation (bips.dev/9); BIP 135 generalised the thresholds. Kaspa's Crescendo was a fixed DAA score: `crescendo_activation: ForkActivation::new(110_165_000)` for mainnet and `88_657_000` for testnet (`vendor/rusty-kaspa/consensus/core/src/config/params.rs` lines 648 and 704; the struct at line 28), with nodes connecting only to protocol version 7 peers from 24 hours before (docs/crescendo-guide.md at v1.0.0). Monero's upgrades are scheduled hard forks, formerly every six months, now every 9 to 12 months (getmonero.org). Igneum's own 6 October incident was a fixed-height activation crossing a half-updated fleet (CLAUDE.md, Devnet 2 rules). Nobody applied Ethereum's dial to a proof-of-work chain's economic parameters. The combination is new; the mechanism is Ethereum's.
|
||||
|
||||
**What Igneum already has.** The header's version bits (O-5.3 candidate), the 60 percent rule, the fee parameters as `Params.fees` per network (spec 5.11), the DAA window every node computes.
|
||||
|
||||
**The model.** At 1/1,024 per block and 1 BPS a dial can move 2.3x in a day (1.001^86,400) if every producer votes the same way, 1.07x if 51 percent do and 49 percent vote the other way (the median moves only when a majority agrees, and then one step per block). A hostile 51 percent can therefore walk a dial to the genesis bound in days; the bound is the defence, as it is on Ethereum (the gas limit has a hard floor and no cap besides the vote).
|
||||
|
||||
**Hours.** 30: the vote field and median rule (10), the clamp and bounds in `Params` (6), tests including the 51 percent walk (8), spec 5.8 text (6).
|
||||
|
||||
**The gate.** On the fast-time harness, 100 producers at 60/40 split: the dial moves toward the 60 side at the predicted rate and stops at the bound; with a 50/50 split it does not move; a producer that votes outside the range is invalid.
|
||||
|
||||
**Per tier.** Miners set the dials with their blocks (Ember shows the vote and defaults to "hold"); a pool votes for its members in mode A and B templates and the member sees it (spec 9.4); provers watch `B_p` and `S_p` move with the fleet's measured shard time instead of waiting for a human; holders and rollup customers see fee floors that track usage; node operators gain one field per header.
|
||||
|
||||
**The Monero core developer's attack.** "You have handed the fee floor to whoever has 51 percent of blocks, with no social veto. On Monero the dynamic block size has a penalty curve exactly so that a majority cannot cheaply walk it; your 1/1,024 is a speed limit, not a cost. A pool with 51 percent lowers `f_p` to its floor, bloats blocks with wash gas it no longer pays for, and the provers eat the backlog." Answer: the base fee is burned in full, so wash gas is never free (ledger E3), and the backlog rule halves `B_p` regardless of the vote (design 4.3); the range bound caps the walk. The point stands that a dial needs a cost curve, not only a speed limit: the prototype should add Monero's shape (a vote away from the median costs the producer a fraction of its subsidy).
|
||||
|
||||
**The Kaspa core developer's attack.** "A per-block vote on a DAG: which blocks vote? Blue blocks of the selected chain's mergesets, in order, and the median over a window is a function of the block's past, fine. But a parameter in force 'at a block' must be the same for every node validating that block: use the value at the block's selected parent's checkpoint, or two honest nodes meter the same transaction at two prices. You have the same determinism bug the proof-record rule had before P11." Answer: correct; the value in force is read at the last certified checkpoint in the block's past, as 3.1's W30 is.
|
||||
|
||||
**Verdict: prototype.** The chain's economic dials follow the fleet without a human; the two attacks give it the two rules (a cost curve, a checkpoint-anchored read) it needs.
|
||||
|
||||
### 3.6 Treasury-less audit funding: bounties from the burn, a review escrow on upgrades
|
||||
|
||||
**The idea.** the project lead removed the dev fund (spec 5.5) and the project pays audits from the Ember dev fee and founders' mined coins (litepaper). The question: money for audits that comes from users paying for something, with no standing address. Two mechanisms. (a) **Burn redirect.** The base fee burns to nobody. A reproducible break submitted under spec 0.5 and accepted by 60 percent of blue blocks over a window redirects the base-fee burn of the next 7 (execution) or 30 (consensus) days to the submitter's address, once, then returns to burning. No address exists between events. (b) **Review escrow.** An upgrade proposal under 5.7 must escrow IGN in a contract that pays reviewers named in the proposal on a 60 percent "review complete" signal, or refunds on failure; the proposer pays, which is a user paying for a thing (the right to propose code).
|
||||
|
||||
**Why nobody shipped it.** Zcash funds development from the block subsidy (NU6: 8 percent to Zcash Community Grants, 12 percent to a protocol lockbox, ZIP 1015); Decred from a 10 percent treasury spent by stakeholder vote, capped at 4 percent of balance a month since January 2026 (DCP-0013); Monero from the CCS, donations off-chain; Optimism from an 850 M OP reserve for retro funding. Bug bounties pay 10 percent of funds at risk (Immunefi's standard) from the protocol's own treasury; Code4rena runs contests at zero platform fee since 2025. Nobody funds audits from a burn redirect, because a burn redirect is a subsidy to a payee by rule, and the chains that wanted that built a treasury. New in form; a dev fund in substance (see the Monero attack).
|
||||
|
||||
**What Igneum already has.** The base-fee burn in both dimensions, the 60 percent signalling, the ledger's break-submission rule (spec 0.5), the proposal registration transaction (spec 5.8).
|
||||
|
||||
**The model** (frontier_model.py section 4):
|
||||
|
||||
| Chain traffic (fraction of full blocks) | Base fee burned per day, IGN | 30-day redirect, IGN | USD at 0.02 | USD at 0.10 |
|
||||
|---|---|---|---|---|
|
||||
| 0.01 | 2,592 | 77,760 | 1,555 | 7,776 |
|
||||
| 0.10 | 25,920 | 777,600 | 15,552 | 77,760 |
|
||||
| 0.50 | 129,600 | 3,888,000 | 77,760 | 388,800 |
|
||||
| 1.00 | 259,200 | 7,776,000 | 155,520 | 777,600 |
|
||||
|
||||
At launch traffic a 30-day redirect is under one audit contest; at half-full blocks it is a serious bounty. The money exists only once the chain is used.
|
||||
|
||||
**Hours.** 20 for the escrow contract and the redirect rule as a proposal kind; 0 for the honest alternative, which already exists.
|
||||
|
||||
**The gate.** None that a simulator settles; the gate is the project lead's: does a per-event, miner-approved payee with no standing address pass the test that removed the dev fund?
|
||||
|
||||
**Per tier.** Miners vote on each payout with their blocks and can refuse all of them; holders see supply that would have burned paid to a named person; a prover, rig, pool user and rollup customer see nothing unless a break affects them; the node operator gains a proposal kind.
|
||||
|
||||
**The Monero core developer's attack.** "This is a dev fund with extra steps. You removed a 5 percent fund because 'a switch that routes money to an address somebody controls is the first thing a critic points at'; you have now written a switch that routes money to an address somebody controls, gated by the same miners who gate everything else, and you have made the miners the judge of which cryptographer gets paid. Our CCS works because it is off-chain and voluntary and no consensus rule touches it. Keep the burn a burn." The attack is right. Answer: there is no counter besides the honest one: the alternative is the one the litepaper already states (client fee, founders' mined coins, grants off-chain), and the ledger should carry this entry as considered and rejected on the same ground as E4.
|
||||
|
||||
**The Kaspa core developer's attack.** "Also a soft target: a miner cartel with 60 percent invents a break, 'accepts' it, and un-burns 30 days of fees to itself. Your spec 0.5 reproducibility rule is a human judgement; consensus cannot check it." Correct.
|
||||
|
||||
**Verdict: watch.** Design it, do not ship it; record it in the ledger beside E4 as the honest answer to "where do audits come from with no fund": from the company's dev fee and from the people who care, in the open.
|
||||
|
||||
### 3.7 Reproducible-build attestations in a registry contract; Ember refuses a release under N of M attestations
|
||||
|
||||
**The idea.** Bitcoin Core builds with Guix and independent builders publish signed attestations of the output hashes to the `guix.sigs` repository (`bitcoin/bitcoin` PR 21462 added `guix-attest` and `guix-verify`; bitcoinops.org reproducible builds). Put the attestation registry on Igneum: a contract where a builder set posts `(release tag, artefact hash, signature)`; the Ember updater refuses to install a release whose artefact hash has fewer than N attestations from M builders listed in the release key's policy, and shows the attesters. Windows exes are already reproducible here (`-Wl,--no-insert-timestamp`, CLAUDE.md), so the hash is well defined.
|
||||
|
||||
**Why nobody shipped it on chain.** Bitcoin keeps its sigs in a Git repository because Bitcoin has no contract state; Ethereum clients attest off-chain. Igneum has an EVM and a signed updater that already checks a manifest hash. The on-chain registry read by the updater: new in placement, not in idea.
|
||||
|
||||
**What Igneum already has.** Reproducible builds on the box (`tools/build-remote.sh`, `cross-remote.sh`), the signed manifest and updater in Ember (litepaper, Ember table), the commit-string check (`tools/ci/commit-string-check.sh`), the release key in genesis (spec 8).
|
||||
|
||||
**The model.** Trust goes from one key (the release key, which on the devnet "acts as the operator", litepaper Governance) to N of M builders, with M growing as outside builders arrive. The failure the rule catches: a release signed by a stolen key whose hash no independent builder reproduced. Cost per release: one transaction per builder.
|
||||
|
||||
**Hours.** 12: the registry contract (4), the updater's N-of-M check and the display (6), the CI step that posts the box's attestation (2).
|
||||
|
||||
**The gate.** A release whose binary is altered after signing is refused by Ember on three machines; a correct release with N attestations installs; the registry shows both.
|
||||
|
||||
**Per tier.** Every miner's updater refuses an unattested binary and names who attested; a pool operator and a node operator get a chain-readable answer to "is this the binary everyone runs"; holders and rollup customers see that the "release key as operator" sentence has a closing mechanism.
|
||||
|
||||
**The Monero core developer's attack.** "Who are the M builders at launch? The founder, under three names. Reproducible builds are only as good as the independence of the builders, and a pseudonymous one-founder project has one builder. Gitian and Guix were worth something because dozens of people with names attested. You are moving a sigs repo on chain; you are not adding a builder." Answer: correct, and the registry is what lets a second builder exist with a public record; the gate should be the first attestation from a machine the project does not own.
|
||||
|
||||
**The Kaspa core developer's attack.** "A node that reads a contract to decide whether to update is a node whose update path depends on the chain being live and unforked; during the 6 October two-sided chain you would have had two registries." Answer: the updater installs nothing while finality is paused, which is a rule worth adding anyway.
|
||||
|
||||
**Verdict: do now.** Twelve hours, and it closes a sentence the litepaper has to carry today.
|
||||
|
||||
### 3.8 Ember as node, wallet and light client for everyone
|
||||
|
||||
**The idea.** Ember already runs a node, mines, proves and keeps a key; Igneum Wallet reads Ember's node when present. Make the one app the client for everyone: a holder who does not mine runs Ember in "verify" mode (the light client of spec 10 inside the same binary, the node card to pin a seed), a miner runs it in full mode. Every miner is a node; every holder is at least a light client; nobody runs a browser wallet against someone else's RPC by default. The node count becomes the miner count plus the holders who chose full mode.
|
||||
|
||||
**Why nobody shipped it.** Bitcoin Core is a node and a wallet but not a miner; miners run separate software (approximate). Monero's GUI runs a node and a wallet and can mine on the CPU (approximate; the litepaper's reason for no CPU lane is botnets). Kaspa's miners run kaspad plus a separate miner. Igneum's Ember already supervises node, miner, prover and key (litepaper, Ember table), so the step is small. Not new; the combination with the light client and the vote key in one binary is Igneum's.
|
||||
|
||||
**What it does to node counts and Sybil counts.** Bitcoin: 24,682 reachable nodes (bitnodes.io, 5 October 2026); Ethereum: 8,136 execution clients on ethernodes.org, 11,781 on Etherscan the same day; Monero: about 5,000 peers seen in 72 hours by one tracker (monero.fail), all secondary. Igneum's gate 4 is 1,000 independent miners; with Ember as the node those are 1,000 full nodes on day one of the public testnet, each with a vote key. Sybil counts are irrelevant on Igneum by design: nothing in consensus counts nodes or keys (spec 3.1 W6, ledger F17), so a Sybil inflates a node map and nothing else. The one place a count matters, "no verifier, no vote" (spec 9.7 item 2), is served better: every member has a verifier because the signer is the verifier.
|
||||
|
||||
**Hours.** 20: the light-client engine inside Ember's process with a mode switch (12), the wallet reading it (4), the node card pinning (4).
|
||||
|
||||
**The gate.** A machine with no GPU runs Ember in verify mode, shows "locked" from a certificate it verified, and sends a transaction; a miner's Ember shows one key in the header of its blocks and the same key signing votes.
|
||||
|
||||
**Per tier.** A home miner runs one program; a holder with a laptop runs a verifier instead of trusting an RPC; a pool user's member process is Ember (spec 9.1); a rig runs one signer and many workers (spec 9.6); the node operator is now everyone.
|
||||
|
||||
**The Monero core developer's attack.** "A node that is also a hot wallet with a vote key and a miner is one process with every secret in it; one bug in the dashboard's local HTTP server (you serve it behind a per-launch token) and the key, the vote and the coins go together. Monero separates the daemon from the wallet for this reason." Answer: the separation stays at the process level (signer, workers, node, wallet are separate processes under one supervisor), and the vote key and the payout key are different keys; the attack names the test (the dashboard token's threat model) that gate 4 must include.
|
||||
|
||||
**The Kaspa core developer's attack.** "Node count is a vanity metric; what matters is who produces blocks and who the honest majority peers with. A thousand Ember nodes behind home NAT accept no inbound connections, so your reachable count is your seed list plus the rigs, and an eclipse of the seed list eclipses the fleet." Answer: correct; spec 10.6's pinned seed list with identity keys and the local peer set is the defence, and the eclipse test O-3.7 with Ember nodes is the gate.
|
||||
|
||||
**Verdict: do now.** Twenty hours; the testnet gate then measures nodes, not only miners.
|
||||
|
||||
### 3.9 Hardware wallets that verify proofs
|
||||
|
||||
**The idea.** A Ledger or Trezor that verifies the wrapped block proof (or the certificate) before signing, so "final" is checked on the device, not in the companion app.
|
||||
|
||||
**Why nobody shipped it.** Ledger's secure element is EAL6+ certified and Trezor's Safe 3 and 5 use an Infineon OPTIGA Trust M (trezor.io; ledger.com), both small, slow, memory-poor chips. The cryptographic work hardware wallets do is signing; the heavy lifting (sync, proofs, history) is in the companion. A bn254 pairing on a Cortex-M-class secure element is seconds to minutes (approximate, from memory; the Bulletproofs-on-Trezor paper, eprint 2020/281, shows what Micropython on a Trezor costs for range proofs, and it is slow). Igneum Wallet already verifies the finality certificate with the node's own code (litepaper, Wallet table), which is the companion doing it.
|
||||
|
||||
**The model.** Certificate verify: G1 aggregation of up to 10,000 keys plus one BLS12-381 pairing; on a laptop in JavaScript 58 to 155 ms (bench-log). A secure element is 100x to 1,000x slower on scalar arithmetic than a laptop core (approximate), so 6 to 150 s per certificate, every 30 s. Not shippable.
|
||||
|
||||
**Hours.** 12 for a companion-side integration (the wallet already does it); the on-device path is not worth hours.
|
||||
|
||||
**Per tier.** Holders get "final means final" in the companion today; nobody gets it on the secure element.
|
||||
|
||||
**The Monero core developer's attack.** "Monero's Ledger app exists and does nothing but sign; it took years and a custom protocol (eprint 2020/281). You will not get a proof verifier onto a secure element and you should not pretend to." Correct.
|
||||
|
||||
**The Kaspa core developer's attack.** "A device that verifies a proof still needs to know which chain tip the proof is of; it will take that from the companion, which is the thing you did not trust." Correct.
|
||||
|
||||
**Verdict: never on the secure element; do the companion verify** (already done in Igneum Wallet 0.1.1; the Ledger and Trezor apps when they exist should display the companion's verified state and sign).
|
||||
|
||||
### 3.10 Proof-of-useful-work: the lottery hash partly a proof
|
||||
|
||||
**The idea as asked.** Make the leader election depend in part on proving work, so the energy that picks the block maker is useful.
|
||||
|
||||
**Why every attempt failed, cited.** Primecoin (2013) found Cunningham and bi-twin prime chains that nobody uses (Bitcoin Magazine, July 2013). Gridcoin pays for BOINC work and stops if BOINC stops (gridcoin.us; the 2022 "Challenges of PoUW" survey, arXiv 2209.03865). Ball, Rosen, Sabin and Vasudevan (eprint 2017/203) gave proofs of useful work from fine-grained problems (Orthogonal Vectors, 3SUM, APSP) and state the conditions: the problem must be sampleable at a tunable hardness with instances the miner cannot choose, and the verifier must be cheaper than the work. Ofelimos (Fitzi, Kiayias, Panagiotakos, Russell, CRYPTO 2022, eprint 2021/1379) got a provably secure protocol by making the work a doubly efficient local search whose usefulness is a side effect and small. The 2026 "Economics of Proof-of-Useful-Work" (arXiv 2606.06700) and the empirical study of Pearl's cuPOW (arXiv 2606.04819, "The Usefulness Gap") find the same gap between the work paid for and the work anyone wanted. I found no Coinbase paper on the subject (searched 6 October 2026); if the project lead has one in mind, its title is needed. Aleo ran proving as the consensus work and the fastest prover won (CLAUDE.md: the Aleo lesson; litepaper precedents table, approximate). Boundless's PoVW (docs.boundless.network/zkc/mining/overview) pays ZKC pro rata to cycles proven per epoch with a stake that scales with the work, which is a reward for proving, not a leader election, and it is on a proof-of-stake chain.
|
||||
|
||||
**The sampleability problem, plainly.** A lottery needs a puzzle whose instances are drawn at random from a distribution the miner cannot steer, whose hardness is tunable by a target, and whose solution is verifiable in milliseconds. zkVM proving has none of these: the instances (segments, jobs) are chosen by users and producers, the hardness is whatever the program is, and the verifier is tens of milliseconds to seconds. Any blend ("a miner's lottery target eases in proportion to its proven cycles last hour") gives the fastest prover more blocks, which is Aleo with a cap, and a cap small enough to be safe is a reward too small to be useful.
|
||||
|
||||
**What Igneum already has instead.** The separation (litepaper: "the lottery and the proving are kept separate on purpose"), the 20 percent pool paid by sortition by weight, PoVW-like cycle metering through pgas.
|
||||
|
||||
**Hours.** 0.
|
||||
|
||||
**Per tier.** Nothing changes; the 12 GB card still earns from proving through the pool.
|
||||
|
||||
**The Monero core developer's attack.** "RandomX's whole point is that the work has no second use, because any second use is a subsidy to whoever does the second thing best, and that is a specialist. The moment your hash is 'partly a proof', the best prover is the best miner, and the best prover is a datacentre. You know this; it is in your own CLAUDE.md."
|
||||
|
||||
**The Kaspa core developer's attack.** "Leader election on a DAG must be a memoryless Poisson process so that GHOSTDAG's k and the orphan analysis hold; a target that depends on the miner's past hour of proving is not memoryless and your blue-set bounds no longer apply."
|
||||
|
||||
**Verdict: never.** Both attacks are correct and the second is fatal to the DAG analysis. The honest version of "useful work" is the one Igneum has: the same card, two jobs, two payments, no coupling. Lane 8 may take one adjacent idea by name: **the shadow-useful puzzle**, in which the program work placed in the latency shadow (class v4, 100,000 ops per hash that cost the card nothing) is itself a small verifiable sub-computation drawn from chain state (a hash-based commitment to a sampled Merkle path of the segment's state witness), so the shadow ops have a second use that does not change who wins. It changes nothing about leader election because the shadow is free; whether a useful shadow program is as chip-hostile as a random one is lane 8's question.
|
||||
|
||||
### 3.11 Miners paid for proving others' chains as the main income, the lottery as the tiebreaker
|
||||
|
||||
**The idea as asked.** Invert the design: proving is the income, the lottery only orders.
|
||||
|
||||
**The arithmetic** (frontier_model.py section 7):
|
||||
|
||||
| Income line | USD per day | Basis |
|
||||
|---|---|---|
|
||||
| Proving every Ethereum L1 block at the Sep 2026 tracker cost | 36 | USD 0.005 x 7,200 blocks; a buyer pays above cost, call it 10x: 360 |
|
||||
| The same at the Dec 2025 cost | 288 | under USD 0.04 per block |
|
||||
| All rollup proving spend (customer brief) | 8,200 to 27,400 | "low millions a year", approximate |
|
||||
| Boundless, trailing day in the explorer, 4 Oct 2026 | 2 | 8.4 T cycles at USD 0.21 per billion, `developer-adoption.md` 2b, approximate |
|
||||
| Igneum year-1 emission at USD 0.005 per IGN | 13,700 | 31.688 IGN per block x 86,400 |
|
||||
| At USD 0.02 | 54,800 | |
|
||||
| At USD 0.10 | 273,800 | |
|
||||
|
||||
The whole public proving market is three to four orders of magnitude under year-1 emission at any price input. The cost curve (section 2.6) falls 3x to 30x a year, so dollars per proof fall as fast as volume rises; for proving to be the main income by 2030, paid demand must grow about 1,000x in dollars. The design's own claim is the defensible one: a second income that keeps cards on after the subsidy fades (spec 5.10.2).
|
||||
|
||||
**Why nobody shipped it.** Succinct and Boundless are exactly this (proving as the income) and have a token for the lottery's role; their provers are datacentre operators (ledger C10). Nobody has made it a GPU home-miner's main income because the market is this size.
|
||||
|
||||
**Hours.** 0.
|
||||
|
||||
**Per tier.** The 12 GB home card earns pool emission today and job income later; the number that matters to it is the pool share, not the market.
|
||||
|
||||
**The Monero core developer's attack.** "Your 'paid, useful, verifiable work' line implies the work pays. It does not and will not; say so in the litepaper's income table." Answer: the litepaper already says "small market today", "upside, not a promise" (ledger P6); the arithmetic above should join it.
|
||||
|
||||
**The Kaspa core developer's attack.** "If proving were the income, the lottery would be a cost centre miners minimise, hash would fall to the floor, and your 51 percent cost would be the cost of a few 5090s. Keep the lottery paid." Correct.
|
||||
|
||||
**Verdict: never by 2030 as the main income; watch the market yearly.**
|
||||
|
||||
### 3.12 The GPU fleet as a public compute market beyond proofs, priced in IGN
|
||||
|
||||
**The idea.** Rendering, inference, transcoding, simulation sold by Igneum miners for IGN, through the same client that switches between hashing and proving.
|
||||
|
||||
**The honest problem.** General compute is unverifiable: a renter cannot tell a rendered frame from a cheaper one, an inference from a smaller model's, without redoing the work. The existing markets answer with trust substitutes: Render uses result quorums for graphics, Akash provider auctions plus reputation, io.net proof-of-work-style attestations (all secondary, io.net's own comparison page and a 2026 DePIN survey). None of those is checkable by a chain.
|
||||
|
||||
**The verifiable subsets, named.**
|
||||
|
||||
| Work | How it is verified | Status for Igneum |
|
||||
|---|---|---|
|
||||
| ZK proving jobs | The proof | The precompile (design 6), Designed |
|
||||
| Deterministic recompute with sampling | Commit to every intermediate, a verifier re-runs a random fraction (Statistical Proof of Execution, arXiv 2503.18899; sampled layerwise proofs for inference, arXiv 2609.27367) | Feasible as an app on the precompile: the sampled chunk is the job; the rest is a commitment |
|
||||
| Rendering with result quorum | Two or three miners render the same frame; the chain pays on agreement (Render's approach, approximate) | An app; the chain pays per agreement, cannot judge quality |
|
||||
| TEE-attested inference | NVIDIA confidential computing attestation on H100 and H200 (phala.com GPU TEE) | The fleet's cards have no TEE; not Igneum's |
|
||||
| Bitwise-reproducible training | Verde-style proofs of learning on a rollup (secondary, io.net comparison page) | Research |
|
||||
|
||||
**Hours.** 60 for a sampled-recompute job type on top of the precompile; 0 for the general market.
|
||||
|
||||
**Per tier.** A 24 GB card could sell sampled-recompute work; an 8 GB card cannot hold most inference models; the rollup customer is unaffected; a holder sees IGN demand only for the verifiable subset.
|
||||
|
||||
**The Monero core developer's attack.** "You would be Golem, Render and Akash with a worse token story and a settlement layer nobody asked for. The honest answer to 'GPU owners should be paid for useful work' is a market with reputation, and reputation is not a consensus rule." Correct for the general case.
|
||||
|
||||
**The Kaspa core developer's attack.** "Every second a card spends on a render is a second off the lottery; the design's own economy model shows hash falling 14 percent when external pay rises 10x (scenario b). A compute market large enough to matter would empty the lottery." Correct, and it is the reason 3.11 is never.
|
||||
|
||||
**Verdict: never for unverifiable work; do the verifiable subsets as apps on the precompile** (the sampled-recompute job type is the one worth 60 hours).
|
||||
|
||||
### 3.13 Igneum as the settlement layer for GPU rental itself
|
||||
|
||||
**The idea.** Vast.ai and RunPod match renters and hosts and take a platform cut; the escrow, the metering and the payout could run on Igneum, where every miner is already a host with a funded wallet and a card that is on.
|
||||
|
||||
**The fee arithmetic** (frontier_model.py section 5):
|
||||
|
||||
| Card | Vast.ai on-demand USD/h | Platform take modelled | Host loses USD per card-year | Igneum settlement per rental (2 transfers at the floor) | At USD 0.02 / 0.10 per IGN |
|
||||
|---|---|---|---|---|---|
|
||||
| RTX 5090 | 0.44 (getdeploying.com, 6 Oct 2026) | Vast about 15 percent (secondary) | 579 | 0.0102 IGN | 0.0002 / 0.0010 |
|
||||
| RTX 5090 | 0.44 | RunPod about 7 percent (secondary: hosts keep 93) | 270 | 0.0102 IGN | |
|
||||
| RTX 4090 | 0.31 | Vast about 15 percent | 408 | 0.0102 IGN | |
|
||||
| RTX 4090 | 0.31 | RunPod about 7 percent | 190 | 0.0102 IGN | |
|
||||
|
||||
Caveat on the takes: secondary comparisons put Vast at about 15 percent and RunPod at about 7 percent; Vast's own June 2024 product update says the host fee was removed and replaced by a surcharge it does not publish, so the 15 percent is a market estimate, not a fee page. The chain's fee is three to five orders of magnitude under either. The platform's take pays for matching, images, dispute, trust and the verification of delivered work, and the last is what the chain cannot do (3.12).
|
||||
|
||||
**What Igneum already has.** Funded miner wallets, the pool protocol's TLS transport and member identity (spec 9), the job escrow shape (design 6), the sampled-recompute path above.
|
||||
|
||||
**Hours.** 40: a rental escrow contract with hourly streaming and a sampled attestation of liveness (the host signs a challenge per minute with the vote key; proves possession of the card by running one lottery warp on it, which the CPU verifier checks in 0.44 ms) (24), a client-side matching list (16). It settles payment and liveness; it does not verify the renter's workload.
|
||||
|
||||
**The gate.** Ten rentals between fleet boxes with one host that goes dark: the escrow pays to the minute of the last valid challenge; the renter's refund is exact; the chain fee per rental under 0.02 IGN.
|
||||
|
||||
**Per tier.** A home miner rents out idle hours with no platform cut and a 30-day public record as a host; a rig lists eight cards; a pool user is unaffected; the prover role and the host role compete for the same seconds; a holder sees IGN demand per rental; a rollup customer is unaffected.
|
||||
|
||||
**The Monero core developer's attack.** "Escrow is 1 percent of a marketplace. The 15 percent is the other 99: the people who answer when a pod dies. You will have a cheaper escrow and no renters, and every renter you do get will be running the thing Vast bans. Also: a card that is rented is a card that is not mining, so you are paying people to leave your lottery." The last point is 3.12's and stands.
|
||||
|
||||
**The Kaspa core developer's attack.** "Streaming payments per minute at 1 BPS are 1,440 transactions a day per rental, each burning a base fee; at a thousand rentals that is your whole block budget. Use a channel, settle twice." Correct, and the model's two transfers assume exactly that.
|
||||
|
||||
**Verdict: prototype** the escrow with the liveness challenge, because it reuses the vote key and the CPU verifier in a way no other chain can, and because miners are hosts already; do not call it a marketplace.
|
||||
|
||||
### 3.14 Proofs sold to AI labs for verifiable inference
|
||||
|
||||
**The state of the art, cited.** zkLLM (arXiv 2404.16109, CCS 2024) proves a 13 B-parameter LLM's inference in under 15 minutes with proofs under 200 kB, verified in 1 to 3 s; the 2026 sampled-layerwise paper (arXiv 2609.27367) measures 803 s of proving per forward pass on LLaMA-2-13B and extrapolates about 18 days per 2,000-token generation under full ZK. EZKL's median proof time on small workloads is about 8.2 s and a 100 M-parameter model is about 10,000 s per proof at today's throughput (proofoftech.org, secondary). Modulus Labs' Remainder prover was benchmarked at USD 0.085 per proof to verify on Base; the team joined Tools for Humanity in late 2024 and no longer sells (proofoftech.org). The competitor is a TEE: NVIDIA confidential computing on H100 and H200 with remote attestation, sold today at near-zero overhead (phala.com; arXiv 2607.19353 benchmarks), and sampling schemes (SPEX, arXiv 2503.18899) that are statistical, not cryptographic.
|
||||
|
||||
**Cost per token, approximate.** 803 s of one GPU per forward pass on a 13 B model at a USD 0.44 5090-hour is about USD 0.10 per token proven. An unverified 13 B token is of the order of USD 0.0000002 (secondary inference pricing pages, 2026). The gap is five to six orders of magnitude.
|
||||
|
||||
**What Igneum could sell by 2030.** Not inference proofs for frontier models. Proofs that a committed small model (under 100 M parameters) produced an output from a committed input, batched; proofs of aggregation over many small inferences; proofs of a sampled layer (the hybrid in arXiv 2609.27367) as a job type. `developer-adoption.md` 2b already draws the line at "verifiable compute, not verifiable AI".
|
||||
|
||||
**Hours.** 40 for a sampled-layer job type once the precompile exists; 0 today.
|
||||
|
||||
**Per tier.** A 24 GB card could prove a small model's inference as a job; nothing for smaller cards; a rollup customer is unaffected.
|
||||
|
||||
**The Monero core developer's attack.** "A lab that wants verifiable inference buys an H100 with a TEE and gets an attestation for free. Your 100,000x-slower proof is for people who do not trust NVIDIA's attestation key, and those people are not buying GPU time from strangers." Fair for 2026 to 2028.
|
||||
|
||||
**The Kaspa core developer's attack.** "Nothing here touches consensus; it is an app on the precompile. Stop listing apps as protocol ideas." Fair.
|
||||
|
||||
**Verdict: watch** the cost curve yearly; the crossing where ZK beats a TEE on cost per token is not in sight by 2030 on the cited numbers.
|
||||
|
||||
### 3.15 The Igneum program pipeline as a verifiable randomness beacon
|
||||
|
||||
**The idea.** The chain already derives an unbiasable seed once an hour: a certified checkpoint, through a 10-minute class-group VDF (spec 04; 516-byte proof, 4.47 ms verify). Run the same VDF on every certified checkpoint hash at a 30-s delay and publish the output: a public randomness beacon at 30-s cadence with no league, no threshold key and no trusted set.
|
||||
|
||||
**What drand is, cited.** The League of Entropy runs drand: threshold BLS over `H(round)` in unchained mode, a 2/3 threshold of a fixed set of organisations (the threshold must exceed 50 percent), quicknet at 3-s rounds since October 2023, timelock encryption built on it (docs.drand.love quicknet post and cryptography page). Its trust assumption is that under a third of a named set collude.
|
||||
|
||||
**The model** (frontier_model.py section 6):
|
||||
|
||||
| Beacon | Period | Latency | Unbiasability | Trust |
|
||||
|---|---|---|---|---|
|
||||
| drand quicknet | 3 s | about 3 s | threshold BLS, under 1/3 of about 20 organisations collude | a league |
|
||||
| Igneum epoch seed today | 3,600 s | 600 s | certified checkpoint plus a VDF the last producer cannot evaluate in time | nobody |
|
||||
| Proposed per-checkpoint beacon | 30 s | 30 to 60 s | the checkpoint is locked by 2/3 of 30-day weight before the VDF starts; a last-block grind costs a block's subsidy per try and buys a bit only if the attacker evaluates the VDF faster than the chain | nobody; the honest limit is the class-group ASIC (Chia's timelords are software or ASIC, docs.chia.net) |
|
||||
|
||||
Chia's hardware timelords are the precedent for "the fastest squarer learns the value first" (Boneh, Bonneau, Bünz, Fisch, eprint 2018/601 for the VDF; Chia's class-group VDF competition repository for the implementation lineage). That is a front-running edge measured in seconds, not a bias.
|
||||
|
||||
**What Igneum already has.** The VDF prototype (`proto-vdf/`), `seed_source` in headers, PREVRANDAO already defined from the epoch VDF (spec 7.1), the certificate every 30 s.
|
||||
|
||||
**Hours.** 24: a 30-s VDF parameter set and the proof relay per checkpoint (12), an RPC and a `wss` feed (6), a contract exposing the latest value and a verify function (6).
|
||||
|
||||
**The gate.** 2,880 values a day on the devnet for a week; every value verified by an independent client in under 5 ms; no value published before its checkpoint locked; a deliberate withholding of the last block before a checkpoint measured for its effect on the output (none, because the checkpoint is what is locked).
|
||||
|
||||
**Per tier.** A node operator evaluates one 30-s VDF per checkpoint (one core); a miner does nothing new; an app developer gets a 30-s beacon and timelock encryption; a holder sees a product that drand's users (lotteries, raffles on Sui, approximate) might pay gas for; a rollup customer could read it through the proof bridge.
|
||||
|
||||
**The Monero core developer's attack.** "Your beacon is only as unbiasable as your finality, and your finality pauses whenever under 2/3 of weight is connected (spec 03). A beacon that stops when the chain is partitioned is not a beacon; drand ran through every outage its members had because it needs a threshold, not a supermajority of all." Answer: correct; the beacon publishes nothing during a pause and must say so, which is still a stronger statement than a league's liveness.
|
||||
|
||||
**The Kaspa core developer's attack.** "A 30-s VDF on a 1-BPS chain is fine; at 10 BPS your checkpoints are still 30 s of DAA time, fine; but the VDF input must be the checkpoint hash as every node agrees it, and your C4 finding showed two honest nodes can hold two certified checkpoints at one index for a window. Two beacons." Answer: the beacon for index i is published only when a single certificate for i is in the past of the next certified checkpoint, which is the F24 re-determination path; one window of delay in the worst case.
|
||||
|
||||
**Verdict: prototype.** Twenty-four hours on code that exists, and a product no proof-of-work chain offers.
|
||||
|
||||
### 3.16 The hourly program swap as a research dataset; the fleet library as a product
|
||||
|
||||
**The idea.** Igneum generates 8,760 random GPU kernels a year, compiles each on Metal, CUDA and OpenCL, races up to 17 variants per card (lever 1, measured +17 to +21 percent on the M5 Max), and logs per-card per-variant timings to the fleet log (lever 2). That corpus does not exist anywhere: a continuous stream of random, bit-exact-across-vendors integer kernels with measured performance on every consumer GPU, under a fixed memory footprint. Publish it (the generator is public with the spec; the timings are the product) and the fleet library (the per-card best-variant table) as a dataset.
|
||||
|
||||
**Who would pay, what for.** Compiler teams (LLVM's NVPTX and AMDGPU backends, Apple's Metal compiler) for a regression corpus with ground truth across vendors; GPU microarchitecture researchers for a latency-bound random-read benchmark across generations (the dependent-read ceilings of `chip-model-v3.md` 5.3 are exactly what such a corpus measures); the project's own cryptanalysts (the weak-program census, `weak-program-census-2026-10-03.md`) for the distribution of program properties. Money: small (research datasets are grants and goodwill, not revenue); standing: large, and it is the public benchmark the litepaper promises for January 2027 made continuous.
|
||||
|
||||
**Why nobody shipped it.** RandomX programs are per hash, interpreted, and never logged; ProgPoW's period changes were never published as a corpus (approximate). New as a dataset.
|
||||
|
||||
**Hours.** 10: a daily export of the fleet log and the generator seed list to a public bucket with a schema (6), a README with the citation form (4).
|
||||
|
||||
**The gate.** One outside group cites it.
|
||||
|
||||
**Per tier.** Every miner's timings are in it (anonymised to card model); a 9070 XT owner sees why their card is 7x worse per joule than a 5090 on dependent reads (`chip-model-v3.md` 5.8); nothing else changes.
|
||||
|
||||
**The Monero core developer's attack.** "A public corpus of your programs with timings is the chip designer's training set." Answer: the generator is public already (github.com/igneum-network/spec) and a chip must run next hour's program, not last year's; what the corpus gives a chip designer is the distribution, which the spec gives too.
|
||||
|
||||
**The Kaspa core developer's attack.** "Not a consensus matter." Correct.
|
||||
|
||||
**Verdict: do now.** Ten hours and it makes the benchmark promise continuous.
|
||||
|
||||
---
|
||||
|
||||
## 4. What would make a Monero or Kaspa core developer say "I had not thought of that"
|
||||
|
||||
Three, with the exact reasoning each would use to attack it. The first two are 3.2 and 3.3 restated as the thing that is new; the third is new in this file.
|
||||
|
||||
### 4.1 Work as the only stake, and it is slashable
|
||||
|
||||
Monero's and Kaspa's shared premise: in proof of work nothing is at stake except the block you are mining, so misbehaviour by a miner outside block production (a bad job, a withheld proof) cannot be punished, only priced. Igneum's finality weight is a quantity that is at stake, is earned by work alone over 30 days, cannot be transferred, and is already stripped for equivocation. Extending the strip to execution-layer faults (3.2) gives proof of work a slashable bond with no coin and no stake class.
|
||||
|
||||
**The Monero developer's attack, verbatim form.** "Then it is stake. You have a class of participants with something to lose that others do not, and a rule that takes it from them for a judgement call. Every argument you make against proof of stake (capture, cartels, nothing-at-stake inverted into everything-at-stake) applies to a stake made of blocks. Worse, your stake depreciates on its own in 30 days, so the rational prover front-loads bad behaviour in the last days of its weight." Answer: the weight is not transferable and not purchasable, which removes capture by capital; the last-days attack is bounded by the 30-day re-earn, and the sortition is proportional to current weight, so a depreciating key is drawn less. The concession: the spec must stop saying "no stake" and say "no coin stake; the only thing at stake is 30 days of public work".
|
||||
|
||||
**The Kaspa developer's attack.** "Any slashing condition needs an objective, deterministic fault; on a DAG 'late' needs a clock, and your clock is DAA score along the carrier's chain, which is deterministic. Fine. But you now have a second use for the weight table that the finality module computes, and the two uses must read the same table at the same block or two honest nodes strip differently. Your proof-record rule needed P11 for this; write the same sentence now." Accepted.
|
||||
|
||||
### 4.2 Finality carried forward inside the execution proof
|
||||
|
||||
The Kaspa premise: finality on a DAG is a fork-choice property computed by every node from the DAG it holds; it cannot be a proof. The Monero premise: a light client trusts whatever gave it the checkpoint. Igneum's segment proof already recurses from genesis; carrying the weight table in it (3.3) makes "certified under rule v2" a public output of the same proof that attests the state root, with the update costing one mergeset per segment, not a 30-day window per proof.
|
||||
|
||||
**The Kaspa developer's attack.** "The proof attests a chain; finality is about the DAG. Your W2 counts blue blocks in the chain block's past, and 'blue' is GHOSTDAG's judgement, which the proof does not recompute (it would have to run GHOSTDAG over k = 18 or 124 anticone sets inside a zkVM). So the proof takes blueness as a witness from the node, and a node that lies about which blocks are blue gives the proof a wrong table. You have proven the arithmetic and trusted the colouring." This is the sharp one. Answer: the colouring is committed by the header (the mergeset and blue set are determined by the parents, which the header commits to), so the witness is checkable against headers the proof also carries; but checking it means running GHOSTDAG's blue-set rule for each merged block inside the guest, which is bounded (anticone size at most k) and unmeasured. The gate for 3.3 must add: cycle count of the GHOSTDAG colouring check per mergeset inside the guest, and if it is too heavy, the colouring stays a witness and the light client's trust row says "blue set from nodes" until it is not.
|
||||
|
||||
**The Monero developer's attack.** "You have made finality depend on your proof system's soundness in the light client. Say so on the card." Already in spec 10.1 for the proof system row; the row must now name finality too.
|
||||
|
||||
### 4.3 The hourly program as an 8,760-question hardware census
|
||||
|
||||
**The idea.** Every hour the chain hands every card a new random program and every card races 17 compiled variants of it and reports which won and how fast (lever 1, measured; lever 2, shipped). A chip built for the lottery cannot look like a GPU on 8,760 different programs a year: its best variant, its timing distribution across programs, its sensitivity to instruction mix are a fingerprint. Make the fingerprint part of the share protocol: a pool records, per member and per epoch, the variant that won and the share-rate ratio between consecutive programs; the chain's observer publishes the distribution per card model from the fleet library; a key whose ratio pattern sits outside every known card's envelope for N epochs is flagged publicly (the share-pattern detector of Counter ASIC item 4, which found Monero's chips by nonce patterns, now with a per-program timing axis a chip must fake 24 times a day).
|
||||
|
||||
**Why it is new.** RandomX programs are per hash and no pool sees their timing; Monero's chip detection used nonce distributions (MoneroCrusher, approximate); ProgPoW audits priced the chip but had no running census. Igneum's hourly swap with per-card racing produces the census as a by-product. New.
|
||||
|
||||
**The Monero developer's attack.** "Timing is self-reported. A chip reports whatever a 4090 would report; it has the 4090's published envelope from your own dataset (3.16). And MoneroCrusher found us the chips not by timing but by nonce patterns, which a chip emulates trivially once it knows you look. Detection that depends on the attacker's cooperation is theatre." Answer: the share rate per epoch is not self-reported; it is the pool's count of verified shares, and a chip that throttles itself to a 4090's per-program envelope on every program forfeits its edge on the programs where it is strong, which is a cost measured in hash. The detector cannot prove a chip; it can price the chip's camouflage. That is the honest claim.
|
||||
|
||||
**The Kaspa developer's attack.** "We welcomed chips, so nothing here is for us. But as engineering: your per-epoch ratio depends on the pool's vardiff and on network luck; the envelope for one card model will be wide, and a 2x chip sits inside it. Your detector finds a 10x chip and misses the 2x one your model says is the threat." Fair: the detector's resolution is the gate (one epoch's share-rate variance per member at one share per 10 s is about 5 percent over an hour; a 2x step is 40 standard deviations, a 1.2x step 4; so it resolves 1.2x in a day and 1.05x in a month, approximate).
|
||||
|
||||
**Hours.** 16: the per-epoch ratio in the pool protocol's `stats` (spec 9.5) and the observer's envelope per card model (12), the public page (4).
|
||||
|
||||
**The gate.** The observer flags a deliberately throttled fleet box (a 5090 capped to a 4070's rate) within 24 epochs, and flags no honest card over a week.
|
||||
|
||||
**Per tier.** Every miner's card model gets an envelope; a home miner on an unusual card (Apple, Intel) must be in the library or will be flagged; pools carry one more statistic; nothing in consensus.
|
||||
|
||||
**Verdict: watch,** then do once the pool protocol exists: it is the cheapest instrument the chain has for the question the chip model cannot answer from a spreadsheet.
|
||||
|
||||
---
|
||||
|
||||
## 5. The incremental list
|
||||
|
||||
Smaller than the sections above; each with hours and a gate.
|
||||
|
||||
| # | Item | Hours | Gate | Why now |
|
||||
|---|---|---|---|---|
|
||||
| I1 | Expose per-key 30-day weight and blue-block count in `IgneumInfo` so hashrate forwards and hardware-finance contracts settle from chain state (`developer-adoption.md` 2c) | 6 | A forward contract settles on the devnet against `getFinalityWeights` with no oracle | The data is already maintained for finality |
|
||||
| I2 | Write the N schedule (latency-shadow program length) into the era draw at genesis, a doubling per era until the verifier gate binds (section 2.3) | 8 (spec text and the draw) | Verifier under 10 ms on a 2019-class core at the year-6 N | HBM4 arrives in 2027 to 2028 and the chain must answer it without a release |
|
||||
| I3 | Define the block-proof target as a function of the fleet's measured median shard time, published per era, not as "under 10 s" (section 2.6) | 4 | The site reads it from the bench table | Honesty about the 12 GB tier |
|
||||
| I4 | A second zkVM implementation of the `ProofSystem` trait (RISC Zero or OpenVM) running on one fleet box as a shadow verifier, so a soundness bug in one system is detected by disagreement before it reaches a light client | 24 | 1,000 segments agree across both; one injected bad proof disagrees | Ledger P7, D6: the veto protects full nodes, nothing protects light clients today |
|
||||
| I5 | The Ember updater installs nothing while finality is paused (3.7's Kaspa attack) | 2 | A paused devnet, a published release, no install | Free |
|
||||
| I6 | A finality-pause page on the site that shows the connected weight fraction live, so the "node reports the pause" sentence has a public face | 4 | Shows tonight's 18:42Z pause from the observer's data | Tonight's incident |
|
||||
| I7 | Equivocation-evidence bounty paid in sortition slots: the key that first carries valid evidence inherits the stripped key's shard assignments for 30 days (no coins move; weight is reassigned, not created) | 12 | Two signers under one key on the fast-time harness; the evidence carrier wins the stripped key's draws | Makes watching for equivocation pay without a treasury |
|
||||
| I8 | Mandatory proofs activation height set from a measured coverage share (spec 7.8 item 10) | 4 | Coverage above 99 percent for 7 days on the devnet | The rule is written and off |
|
||||
| I9 | The exclusive window at 25 s and the claim timeout at 120 s on the phase 4 devnet (decided by the project lead, P9) with the economy simulator re-run at the measured shard times from `prover-tiers-real-cards.md` instead of the 20-s target | 6 | The 3060 class's shard share within 5 points of its weight share | The inputs changed today |
|
||||
| I10 | `eth_getProof`, `debug_traceTransaction`, `eth_subscribe` (D5 step 2) before any outside team | 24 | Foundry's debugger and the Blockscout fork run against a devnet node | The light client and every tool depend on `eth_getProof` |
|
||||
| I11 | Register chain ids 4461 to 4463 on ethereum-lists/chains before the public testnet (spec 7.1) | 1 | The PR merged | Wallets |
|
||||
| I12 | Publish the 2028 tier table (section 2.6) on the miner page with its three rates, so no card owner buys on a promise | 2 | Live | the project lead's consequences rule |
|
||||
| I13 | A spec sentence in 03 and 05: "no coin stake; the only thing at stake is 30 days of public work" (4.1) | 1 | Text | Before 3.2 is prototyped |
|
||||
| I14 | The litepaper's income table gains the proving-market arithmetic of 3.11 in one line | 1 | Text | Ledger P6 asked for honesty; the number makes it concrete |
|
||||
| I15 | A ledger entry beside E4 recording 3.6 as considered and rejected on E4's ground | 1 | Text | So the question is not re-asked |
|
||||
|
||||
---
|
||||
|
||||
## 6. Open questions and what I could not run
|
||||
|
||||
- **The BLS verification cycle count inside the SP1 guest** (3.3's gate a) needs a 24 GB card; PC 2 and the fleet were on the class v4 rehearsal and the Devnet 2 block-rate runs tonight. Without it, the 3 percent proving-capacity cost is an estimate.
|
||||
- **The GHOSTDAG colouring check inside the guest** (4.2's Kaspa attack) is unmeasured and may be the real cost of 3.3; it is added to that gate.
|
||||
- **The wrapper** (3.4) does not exist in the repository (ledger P3, R4); the 16 hours include building it on a fleet card.
|
||||
- **HBM4 energy per random read** (section 2.3) is an unsourced estimate (1.0 nJ); JEDEC timing is behind the paywall; the 2x channel count is cited, the tFAW-per-channel assumption is mine.
|
||||
- **The rental-tax rule's determinism** (3.1) depends on reading W30 and H_now at a checkpoint in the block's past; the lag's effect on the renter's first 30 s is unmodelled.
|
||||
- **Vast.ai's actual take** is unpublished; the 15 percent is secondary.
|
||||
- **No Coinbase paper on useful work was found**; if one exists its title is needed to cite it.
|
||||
- **`block-rate-devnet2.md`** was a template at writing time; the 10 BPS question matters for 3.3 (segments re-cut at reorgs) and 3.5 (votes per block), and should be re-read when RUN_A lands.
|
||||
- **Lane 8** holds the shadow-useful puzzle (3.10's handover) and anything about new puzzle shapes; nothing here designs a puzzle.
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary for the coordinator
|
||||
|
||||
Lane 7 read the spec, the litepaper, the ledger sections asked, the design files, the chip model and the fleet's eleven-card table, searched prior art for sixteen ideas, and wrote one arithmetic model (`sim/horizon/frontier/frontier_model.py`) behind every number. Three findings:
|
||||
|
||||
1. **HBM4 raises the stored-dataset chip's per-joule edge from about 7x to about 11x bare and from about 2.3x to about 2.7x under the class v4 latency shadow at N = 100,000 (model 1.4, every chip figure arithmetic), because JEDEC doubled channels per stack (16 to 32); N = 200,000 brings it to 1.7x and N = 330,000 to 1.3x at k = 1. The N schedule belongs in the era draw at genesis (I2), with 10x of verifier headroom.**
|
||||
2. **Vote weight is a slashable, non-purchasable bond (3.2): a key with 0.1 percent of hash has 16,427 IGN of 30-day pool income and its vote at risk against a designed coin bond of 0.0015 IGN per job (model section 3). The design's "no stake" must become "no coin stake".**
|
||||
3. **The consensus proof can be incremental (3.3): carry the W2 table inside the recursive segment proof and update it by one mergeset per segment, with one BLS verify per 30 s (about one shard's budget, approximate, unmeasured). It is the only road to a browser that trusts no node for the voter set, and its real cost is the GHOSTDAG colouring check inside the guest (4.2), which is the first measurement to run.**
|
||||
|
||||
Two honest nevers with arithmetic: proving others' chains cannot be the main income by 2030 (all of Ethereum L1's proving is USD 36 a day at the Sep 2026 tracker cost against USD 13,700 a day of year-1 emission at USD 0.005; 3.11), and the lottery hash cannot be partly a proof without re-opening Aleo and breaking the DAG's memoryless election (3.10). One rule for main: the litepaper's "proving: a second income" line should carry the 3.11 arithmetic (I14), and the spec should carry the "no coin stake" sentence (I13) before any work-stake prototype starts.
|
||||
244
docs/analysis/horizon/network.md
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
# Horizon lane 5: network. Block rate, propagation, node cost, bandwidth, pruning and the snapshot path
|
||||
|
||||
6 October 2026, evening UK. Lane 5 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon`, branch `horizon`. Scripts in `sim/horizon/network/` (README there). Nothing live was touched: the Devnet 2 seed log on igneum-build-1 and `~/Desktop/fleet/bps/A.jsonl` were read, never written.
|
||||
|
||||
What was read: `docs/spec/02-consensus.md` (2.1 parameters, 2.3 the difficulty rule, 2.4 header, 2.5 emission), `08-client-security.md`, `10-light-client.md`; the fud-close worktree's `docs/spec/03-finality.md` C1 and 3.4.2 (vote item 281 bytes, the bitmap and per-block bounds); `docs/fud-ledger.md` M20, M21 (block sizes, k re-derived, the 490 KB body run), X20, M30; `docs/bench-log.md` entries "4 October 2026, cloud devnet" (inter-region RTT and propagation), "ledger M30" (RSS, the 256 MiB cache, the s8 steady slope), "6 October 2026, 12:25 to 13:20Z, the finality route" (the seed as the only peer, the route overflow), "Rental cost of hash, 6 October 2026"; `docs/plans/hands-on-build-1.md` (node 1 and the observer: data dirs 856 and 822 MB, the 127.6 MB exec snapshot), `seed-nodes.md`, `cloud-devnet.md`, `release-0.3.14.md` (the snapshot path and the restart pin), `release-0.3.13.md` 4a; the fleet worktree's `docs/analysis/block-rate-devnet2.md` (read at 19:5xZ and again at 20:1xZ: RUN_A, RUN_B, TIER_TABLE and RECOMMENDATION are still placeholders), `tools/fleet/bps-collect.py`, `box-dn2.sh` (one `--addpeer=<seed>`: the star), `devnet2-override.json`, `docs/bench-log.md` of that branch; the live record of run A: `~/Desktop/fleet/bps/A.jsonl` (43 rows, 19:18 to 19:51Z), `collect-A.log`, `runA-start2.log`, `runB.sh` (run B starts at 20:25Z) and the seed's log `/home/build/dn2seed-A.log` on igneum-build-1 (20,529 lines, 19:17 to 19:59Z, read over ssh); `vendor/rusty-kaspa` `consensus/core/src/config/bps.rs` (the k table, `calculate_ghostdag_k`, parents, mergeset, pruning depth), `constants.rs` (delay bound 5 s, delta 0.01, DAA window), `params.rs` (mainnet `BlockrateParams::new::<10>()`, Crescendo activation 110,165,000), `consensus/src/processes/difficulty.rs` and `window.rs` (the window holds every mergeset block, blue and red), `docs/crescendo-guide.md` (10-bps node requirements); the fork `vendor/igneum-node` (read-only) at `release-0.3.14-node`: `consensus/core/src/igneum.rs` `difficulty` (CAP_BLOCKS 20, the sanitised clock, the clamps), `consensus/src/processes/difficulty.rs` `igneum_difficulty_bits` (blue-work steps on the selected chain), `consensus/src/processes/finality.rs` (MAX_VOTES_PER_BLOCK 48, KEEP_CHECKPOINTS 2,000), `igneum/exec/src/proving.rs` `p2p_snapshot_gate` and `on_exec_snapshot`, `igneum/exec/src/snapshot.rs`; the fork branch `devnet2-bps` 1279a1d6 (`IGNEUMD_DEVNET_BPS`); lane 3's `finality-and-weight.md` sections 5.5 and 8 (the aggregation path tonight), lane 4's `sim/horizon/consensus-security/ghostdag_results_{1,10}bps.md`.
|
||||
|
||||
## 1. The question and the answer in one paragraph
|
||||
|
||||
the project lead asked for Kaspa's answer to solo-miner variance: a higher block rate. Run A ran Devnet 2 at 10 blocks per second through one seed and produced 77 percent red blocks, 321 tips and a 55-block reorg. The propagation model says the links and the star did not do that: with the measured latencies it predicts under 0.1 percent red at 10 bps in a star and in a mesh. What did it is the seed's CPU per block, measured at 61 ms (narrow DAG) to 345 ms (mergeset 150 to 200), against a budget of 100 ms per block at 10 bps; with that cost in the model the star gives 47 to 88 percent red and queueing waits of 26 to 1,769 s, which are the "Accepted 100 blocks via relay" batches in the log. The difficulty rule then read blue work over a chain step capped at 2 s and hardened until the DAG ran at 2 s / (chain-step spacing) of target (model 4 blocks/s at a 5-s spacing, record 3.3 to 3.6), while a narrower-but-still-wide DAG would have made it ease (the direction main reported); counting every mergeset block over the real span, as Kaspa's window does, is unbiased in both regimes. The block rate for the public testnet is 1 bps; 10 bps is a gated step that needs the per-block node cost under 50 ms on a laptop core at a mergeset of 248, the checkpoint interval and the clock cap re-denominated in DAA seconds, and vote aggregation, because with C1 in blue blocks 8,192 voters at 10 bps are 66 GB per node per day of votes.
|
||||
|
||||
## 2. Method
|
||||
|
||||
| Step | What | Where | Machine, lock |
|
||||
|---|---|---|---|
|
||||
| Record | run A's per-minute rows (blocks, blue score, tips, peers, exec tip, CPU, RSS, bytes), the seed log's `PoW accepted` per minute, `Processed` lines (parents, mergeset per 10 s), `Finality: checkpoint N determined` (blue score against DAA), reorg lines, route drops | `~/Desktop/fleet/bps/A.jsonl`; `/home/build/dn2seed-A.log` on igneum-build-1 | read only |
|
||||
| Propagation | event simulation: Poisson production, star or mesh, lognormal links, inv/request/block hops, a hub (or every node) as a single server with a per-block cost, GHOSTDAG colouring with the first k-cluster condition, parent and mergeset caps, reorg depth at miner 0 | `sim/horizon/network/propagation.py`, `results.md`, `results-2.md` | Mac, `with-lock.sh run nice -n 19`, seed 7 |
|
||||
| Controller | the fork's estimator against a wide DAG in closed loop with the 3% / 10% clamps, against the whole-DAG estimator and a red-corrected blue estimator | `controller.py`, `controller-10bps.md`, `controller-1bps.md` | Mac, seed-free (deterministic) |
|
||||
| Arithmetic | k and parameters per rate, votes and bounds, bytes per node per day, CPU budget, RSS and disk, pruned and archival growth, subsidy and payout intervals, finality timing, light-client bytes | `cost.py`, `cost-tables.md` | Mac |
|
||||
|
||||
Nothing was built. No node ran. The fleet's run B (1 bps control, starts 20:25Z) and the mesh variant A2 (not scripted in the fleet worktree at 20:1xZ: no `mesh` or `A2` in `tools/fleet/` or its plans) had not landed when this file closed; section 7 names what they owe.
|
||||
|
||||
## 3. Evidence
|
||||
|
||||
### 3.1 Run A, measured (igneum-devnet-2, 10 bps, 41 miners through the seed on igneum-build-1, genesis bits 505413632)
|
||||
|
||||
Phases from the seed log's checkpoint series (blue score against DAA score; red share = 1 - blue / DAA over the interval) and `Processed` lines; CPU per block from `A.jsonl` `cpu_rss` (ps lifetime %CPU times elapsed, differenced) over the accepted-block deltas.
|
||||
|
||||
| Window (Z) | Production at the seed, blocks/s | Blue rate, blocks/s | Red share over the window | Mergeset per block (Processed) | Parents | Tips at the seed | Hub CPU per accepted block | Hub RSS | What the log shows |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 19:23 to 19:29 | pods join (peers=1 each, blocks=0 synced=false on some), 26 blocks/s peak at 19:30 | | 33% (cp 1 to 29) | 1 to 35 | 1 to 16 | 6 to 30 (pods) | 61 ms | 0.45 to 1.9 GB | the 55-block reorg at 19:27:41Z "unwinding to height 1" (a late-joining pod's chain from genesis) |
|
||||
| 19:29 to 19:31 | 19 to 26 | 1.3 | 77% (cp 29 to 37) | 29 to 36 | 15 | | 115 ms | 1.9 to 2.5 GB | |
|
||||
| 19:31 to 19:37 | 13 to 19 | 0.7 | 94% (cp 37 to 45) | 53 to 197 | 12 to 15 | 295 | | 2.5 to 4.0 GB | `Accepted 97 / 100 / 31 blocks ... via relay` batches; headers ahead of blocks |
|
||||
| 19:37 to 19:41 | 8 to 14 | 0.5 | 95% (cp 45 to 49) | 150 to 190 | 12 | 218 to 295 | 345 ms (19:37 to 19:51 mean) | 4.0 to 4.5 GB | window filled at 19:37Z; first lock cp 47 at 19:40:08Z |
|
||||
| 19:41 to 19:47 | 3 to 7 | 1.0 | 50 to 88% (cp 49 to 61) | 80 to 125 | 13 to 15 | 230 to 350 | | 4.6 to 5.2 GB | `incoming route for IgneumFinality is full, message dropped ... the peer stays`: 109 to 2,547 drops per peer by 19:56Z |
|
||||
| 19:47 to 19:59 | 3.3 to 3.7 | 1.4 to 1.8 | 41 to 56% (cp 61 to 92) | 47 to 90 | 15 to 16 | 348 to 396 | | 5.4 GB at 19:51 | 16 of 47 checkpoints after the window filled LOCKED (cp 47 to 79); cp 61 determined 19:47:00, locked 19:47:18 |
|
||||
| Cumulative to 19:59:32Z | 6.1 (DAA 12,233 in 2,012 s) | 1.38 (blue 2,786) | 77.2% | | | | | | main's 12.4 blocks/s, 77 percent, 321 tips, max reorg 55 are the 19:3xZ to 19:45Z readings of the same record |
|
||||
|
||||
Other measured rows of the run: 421 selected-chain reorgs at the seed (132 of depth 1, 62 of 2, 42 of 3, 25 of 4, 19 of 5; 2 of 43, 1 of 44, 1 of 55); the seed's `rx_tx` counter (netns-wide, so an upper bound) 325 MB in and 2,133 MB out between 19:37:05 and 19:51:15Z (850 s, 4,284 accepted blocks): 2.5 MB/s out, 61 KB/s per peer, about 12 KB per block per peer (a block with its finality section of up to 48 votes at 281 bytes is about 14 KB); exec tip 154 chain blocks at 19:51Z against 11,239 blocks (the executor follows the selected chain, which advanced about one chain block per 10 s at the widest). The run's difficulty values are not in the record: `bps-collect.py` strips `difficulty=` from the watch line before storing it and the node log carries no bits; main's statement that the rule "lowered difficulty" is therefore unverified here, and the production curve (26 to 3.5 blocks/s at a hash the pods' peers=41 say stayed connected) is the measured fact section 5.4 reads.
|
||||
|
||||
### 3.2 Measured propagation and block sizes (the inputs the model takes)
|
||||
|
||||
| Quantity | Value | Source | Label |
|
||||
|---|---|---|---|
|
||||
| Inter-region RTT | 35 ms (hel1-fsn1) to 289 ms (sin-ash); 0.4 to 0.7 inside a location | bench-log, cloud devnet 4 Oct | measured |
|
||||
| Block propagation to 80% of 12 nodes over about 3 hops, 723-byte bodies | p50 343, p90 497, p99 666, max 2,313 ms | same | measured |
|
||||
| One relay hop on 100-ms proxied links (inv, request, block) | p50 318 to 329 ms; own-node processing 6 to 16 ms | fud-ledger M21 run, 5 Oct | measured |
|
||||
| Two hops with 490 KB bodies | p50 641, p99 812, max 857 ms (59-byte bodies: 626 / 1,093 / 2,099) | same | measured |
|
||||
| Live devnet block, 1 coinbase, 22 keys' votes partly | p50 723 B, p90 1,022, max 6,908; coinbase payload p50 395, max 6,580 | fud-ledger M21 sweep | measured |
|
||||
| Vote item, certificate, proof record | 281 B; 273 B + V/8; 274 B | spec 03 3.4.2 (fud-close), fork | cited |
|
||||
| k from the measured delays at 1 bps | p99 0.67 s gives k 5, max 2.3 s gives k 10; k 18 is the 5-s bound | fud-ledger M21, `calculate_ghostdag_k` | cited |
|
||||
| Kaspa 10-bps node requirements | 8 cores, 16 GB RAM, 256 GB SSD, 40 Mbit/s minimum; 12 to 16 cores, 32 GB preferred | `vendor/rusty-kaspa/docs/crescendo-guide.md` | cited |
|
||||
|
||||
### 3.3 Measured node figures
|
||||
|
||||
| Quantity | Value | Source | Label |
|
||||
|---|---|---|---|
|
||||
| PoW cache | 256 MiB per day key, KEEP_DAYS 3, 768 MiB worst, 512 MiB around midnight UTC | bench-log M30 entry | measured |
|
||||
| RSS slope, narrow DAG, 60x fast time, 1 block/s | 30.2 MB per 1,000 blocks (s8 steady, 1,500 blocks) | same | measured |
|
||||
| The live app node on the devnet profile | 1,081 MB at 27 min, 2,258 MB at 4 h 14 min (about 80 KB per block, derived) | same | measured, derived |
|
||||
| Run A's seed | 153 MB before the chain, 5.42 GB at 12,000 blocks (about 440 KB per block; 41 peers, mergeset up to 197) | A.jsonl | measured |
|
||||
| Exec snapshot | 127,564,588 B at tip 141,700 chain blocks (0.9 KB per chain block); `ExecState.records` 1 to 2 KB per chain block | hands-on-build-1.md; M30 note | measured; approximate |
|
||||
| Node 1 and observer data dirs after 3 days | 856 MB and 822 MB | hands-on-build-1.md | measured |
|
||||
| Lottery verify per header | class v3 2.79 ms on a loaded M5 Max core; class v4 4.90 steady, 5.06 cold, 8.23 ms on a half core (box proxy); gate 10 ms | consequences C29; lane 2 section 5.5; spec 01 | measured |
|
||||
| Hub CPU per accepted block at 10 bps | 61 ms (mergeset about 8), 115 ms (about 30), 345 ms (150 to 200) | A.jsonl, section 3.1 | measured |
|
||||
|
||||
### 3.4 What the fleet still owes (read `block-rate-devnet2.md` at 20:1xZ)
|
||||
|
||||
RUN_A, RUN_B, TIER_TABLE and RECOMMENDATION are placeholders. Run B (1 bps, same boxes, fresh genesis, 30 min) starts at 20:25Z by `runB.sh` and its rows land about 21:00Z; the mesh variant A2 is not scripted (`box-dn2.sh` takes one `--addpeer`). The collector's `red` field is 0 in every row (its grep pattern matches no log line), so the fleet's red share must come from blue score against DAA as section 3.1 does; its `--report` payout arithmetic uses 28 MH/s for a 4070 where the brief uses 25. Section 5.2 states this lane's prediction for A2 before its rows land.
|
||||
|
||||
## 4. Model
|
||||
|
||||
Inputs are labelled measured (M), cited (C), simulated (S) or approximate (A).
|
||||
|
||||
1. **k and the parameters per rate** (C, `bps.rs`): k = min k with P(Poisson(2 D lambda) > k) < 0.01 at D = 5 s: 18, 124, 362 at 1, 10, 32 bps; 1,074 at 100 bps (the table stops at 32; `calculate_ghostdag_k` in f64 underflows at x = 1,000, the lane's `cost.py` does it in log space). Parents = clamp(k/2, 10, 16); mergeset limit = clamp(2k, 180, 512); merge depth 3,600 bps; finality 43,200 bps; pruning max(108,000 bps, the Prunality lower bound); maturity 100 bps.
|
||||
2. **Red share from topology and delay** (S, `propagation.py`): blocks arrive as Poisson(bps) split over miners; a block reaches a peer after one hop = 3 lognormal link latencies (median `--link`, sigma 0.29 from the cloud devnet's p50/p90) plus 10 ms processing; a star hub (or every node, `--node-s0`) is a single server with service s0 + s1 x mergeset ms and a FIFO queue; each block's colour is GHOSTDAG's first k-cluster condition over its own past (deterministic given parents); red share = reds in the mergesets of the final selected chain over merged blocks. The abstract rule behind it: reds appear when blocks in flight 2 d lambda exceed k, where d is the effective delay including queueing.
|
||||
3. **Hub queue** (A, M/M/1 reading): utilisation rho = lambda x s; the knee is rho = 1: s = 100 ms at 10 bps, 1 s at 1 bps, 31 ms at 32 bps, 10 ms at 100 bps. Above the knee the wait grows without bound and d becomes the wait, not the link.
|
||||
4. **The controller against a wide DAG** (C for the rule, `controller.py` for the loop): the fork walks the selected chain; a step carries work = blue_work(b) - blue_work(selected parent) (the mergeset blues only) and solvetime = min(clock step, 20 T) (`igneum.rs` CAP_BLOCKS, `difficulty.rs` `igneum_difficulty_bits`). With chain-step spacing sigma = max(1/lambda, d), mergeset m = lambda sigma and blues = min(m, k + 1): estimate / true hash = (blues / m) x (sigma / min(sigma, cap)). Two biases: when sigma > cap the step is capped and the rule over-reads by sigma / cap and hardens to a DAG rate of target x cap / sigma; when the DAG is wide (m > k + 1) and sigma <= cap it under-reads by (k + 1) / m and eases. Kaspa's window (`window.rs` `push_mergeset`: every mergeset block above the blue-score floor, blue and red; `difficulty.rs` `calculate_difficulty_bits`: average target x measured span / expected span) reads the whole-DAG rate over the real span: estimate / true = 1 in both regimes. A blue estimator divided by (1 - observed red share) is the same quantity.
|
||||
5. **Bytes per node per day** (C+S+A, `cost.py` section 3): blocks per day x (header 286 + 32 x parents + body 300 + 274 / 8) + relay overhead (degree x 40 B inv + 40 B request per block, Kaspa's blockrelay flow, A) + votes V x checkpoints per day x 281 + one certificate (273 + V / 8) per block. Checkpoints per day = 2,880 x bps if C1 stays "every 30 blue blocks", 2,880 if it is re-denominated in DAA seconds.
|
||||
6. **CPU budget** (A): the header pipeline validates in order, so per-block cost x bps must stay under about 0.8 core: 800 ms at 1 bps, 80 at 10, 25 at 32, 8 at 100. Cost = lottery verify (M) + BLS per carried vote (A 1.5 ms, unmeasured on this stack, O-10.3) + GHOSTDAG and reachability (O(k x mergeset) store reads; M at the hub only as a total) + exec and record checks.
|
||||
7. **RSS and disk** (M slopes, A steady state): caches 768 MiB worst plus the DAG store's growth per block (30, 80 or 440 KB measured in three settings) over the pruning window; disk per day = blocks x bytes + votes + exec records; archival = the same per year without pruning.
|
||||
8. **Payout and subsidy** (C): subsidy per block = 31.688 IGN / bps at full ramp, 80% to the producer; interval = 1 / (bps x miner hash / network hash).
|
||||
9. **Finality timing** (C, spec 03 C1): checkpoint every 30 blue blocks, determination d = 60 blue (placeholder, 20 on the devnet), lock about 3 s after determination: cadence 30 / bps s, lock (60 / bps + 3) s if C1 and d stay in blue blocks.
|
||||
|
||||
## 5. Results
|
||||
|
||||
### 5.1 Propagation alone: red share and reorg depth per rate and delay (mesh of degree 8, 42 nodes, ideal nodes; `results.md` section D)
|
||||
|
||||
| bps | k | hop delay median ms | red share | mergeset mean / max | tips mean | reorg max / p99 at miner 0 | delay to 90% of nodes, s |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | 18 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 1.1 / 2 to 2.3 / 7 | 1.1 to 2.4 | 1 / 1 to 2 / 2 | 0.09 to 1.82 |
|
||||
| 10 | 124 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 1.7 / 6 to 9.2 / 22 | 1.7 to 8.1 | 2 / 1 to 4 / 3 | 0.09 to 1.82 |
|
||||
| 32 | 362 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 2.9 / 9 to 18.8 / 42 | 2.9 to 14.4 | 3 / 2 to 6 / 5 | 0.09 to 1.82 |
|
||||
| 100 | 1,074 | 50 / 100 / 300 / 1,000 | 0.0% in every cell | 6.0 / 17 to 31.1 / 104 | 5.6 to 25.3 | 4 / 3 to 8 / 8 | 0.09 to 1.82 |
|
||||
|
||||
Reading. Kaspa's k is derived for a 5-s delay bound; at the measured delays (under 1 s per hop, under 2 s to 90 percent of nodes) blocks in flight stay far under k at every rate and no block turns red. The natural reorg depth is the DAG width: 2 to 4 chain blocks at 10 bps, 8 at 100 bps with 1-s hops. Lane 4's simulator (uniform one-way delay to every node, 8 miners) reads p99 11 at 10 bps with d = 0.35 s and 99 with d = 2 s (`ghostdag_results_10bps.md` section 1); the two agree in shape (depth grows with bps x d) and differ in the delay model, so the fleet's measured reorg distribution at 10 bps (section 3.1: 421 reorgs, p50 1, 4 over 40) is the arbiter: outside the start artefact and the saturated phase it sits at 1 to 8, inside this lane's model. Kaspa's published 10-bps red rates are not in the clone (`docs/crescendo-guide.md` carries requirements only); from the k derivation the design red rate is under 1 percent of blocks (delta 0.01 on anticones, approximate), which both simulators reproduce.
|
||||
|
||||
### 5.2 Run A predicted from topology, then from the hub's cost (`results.md` A to C, `results-2.md` E, F, I)
|
||||
|
||||
| Model input | Red share | Hub wait | Hub utilisation | Mergeset mean / max | Reorg max | What it says |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Star, 41 miners, link 40 ms, ideal hub, run A's measured production schedule, join spread 360 s | 0.0% | 0 | 0 | 3.5 / 14 | 4 | topology and latency alone predict no reds at 10 bps |
|
||||
| Star, hub cost 6 + 2 x mergeset ms (a linear GHOSTDAG cost), same schedule | 0.0% | 0.00 s | 0.14 | 3.7 / 15 | 3 | a hub under 20 ms per block keeps up |
|
||||
| Star, hub cost 61 ms per block (M, mergeset 8), same schedule, 1,200 s | 46.8% | 26 s | 0.65 mean (1.6 during the 26 blocks/s burst) | 8.8 / 194 | 43 | the genesis burst alone saturates a 61-ms hub for 5 minutes |
|
||||
| Star, hub cost 115 ms (M, mergeset 30) | 88.1% | 321 s | 1.23 | 20.7 / 199 | 2 | the measured mid-run regime |
|
||||
| Star, hub cost 345 ms (M, wide DAG) | 82.6% | 1,769 s | 3.66 | 9.8 / 45 | 3 | relay in batches, most blocks unmerged at the end |
|
||||
| Measured run A, cumulative | 77.2% | relay batches of 100 blocks | 180% of one core sustained (section 3.1) | 8 to 197 | 55 (start artefact) | |
|
||||
| Star at a constant 10 bps, hub cost 20 / 40 / 60 / 80 ms | 0.0% | 0.00 / 0.01 / 0.05 / 0.18 s | 0.20 / 0.39 / 0.61 / 0.81 | 3.5 to 4.9 | 2 to 4 | under the knee |
|
||||
| the same, 100 / 150 ms | 15.2% / 75.2% | 8.5 / 157 s | 1.01 / 1.51 | 20 / 116 and 27 / 66 | 8 / 3 | the knee is 100 ms per block at 10 bps |
|
||||
| Star at 10 bps, ideal hub, link 100 / 200 / 300 ms | 0.0% | 0 | 0 | 5.8 / 15, 10 / 20, 12.4 / 23 | 3 to 4 | pure latency up to a 2.2-s delay to 90% of nodes gives no reds |
|
||||
| Star with the 26 blocks/s burst for 5 min then 10 bps, hub 6 + 2 m | 0.0% | 0.01 s | 0.33 | 5.4 / 17 | 3 | the burst alone is harmless to a cheap hub |
|
||||
|
||||
So the model predicts run A's red share from the hub's measured per-block CPU and not from its topology: 47 to 88 percent against 77 percent measured, with the waits that the log's relay batches show. The prediction for the mesh variant A2 (`results-2.md` G): a mesh of degree 8 whose nodes each pay 40 or 80 ms per block runs 10 bps at 0 percent red (node wait 0.01 and 0.12 s); at 115 ms per block every node is its own hub and the mesh goes to 52 percent red with 26-s waits and reorgs of 16. The pods run the same binary as the seed on smaller CPUs, so A2 with tonight's genesis bits (a 26 blocks/s burst) is predicted red again; A2 with genesis bits set for 10 bps at 2 GH/s and every node started synced is predicted under 5 percent red if the per-block cost on a pod is under 80 ms, and 50 percent or more if it is 115 ms. The control at 1 bps (`results-2.md` H): 115 or 345 ms per block gives 0 percent red and waits of 0.01 to 0.07 s, which is the live devnet's experience.
|
||||
|
||||
### 5.3 What the hub's 61 to 345 ms per block is made of (the breakdown is not measured; the candidates and their bounds)
|
||||
|
||||
| Component | Per block at 10 bps | Label | Note |
|
||||
|---|---|---|---|
|
||||
| Lottery verify, class v3 (the Devnet 2 override activates v3 at epoch 1) | 2.8 ms | M | one warp per header |
|
||||
| BLS verification of carried votes, up to 48 per block | up to 72 ms at 1.5 ms per verify | A | run A's blocks carried up to 48 of 42 voters' votes; the route drops say the finality path was the hot one |
|
||||
| GHOSTDAG and reachability at mergeset m, k 124 | O(k x m) store reads: 1,000 at m 8, 25,000 at m 200 | C (protocol.rs shape) | the 61 to 345 ms rise tracks m |
|
||||
| Relay to 41 peers (serialise 14 KB x 41, inv handling) | 2.5 MB/s out measured | M | a mesh node of degree 8 does one fifth of it |
|
||||
| Exec of chain blocks, record checks | small: one chain block per 10 s at the widest | M | |
|
||||
|
||||
The gate that settles it is a profile (proposal 5). Whatever the split, the serial budget rule of section 4.6 is the design constraint for every block-rate step: at 10 bps the whole per-block path must stay under 80 ms on the slowest node the network wants to keep, at the mergeset limit 248, not at the narrow-DAG average.
|
||||
|
||||
### 5.4 The controller: what the record shows and what the model says (`controller-10bps.md`, `controller-1bps.md`)
|
||||
|
||||
| Regime (10 bps, k 124, cap 2 s) | Fork's estimator (blue work over the capped chain step) | Whole-DAG estimator (Kaspa's window shape) | Blue estimator corrected by (1 - r) |
|
||||
|---|---|---|---|
|
||||
| d = 0.3 / 1 / 2 s | DAG rate 10.00, red 0%, estimate 1.00x | 10.00, 1.00x | 10.00, 1.00x |
|
||||
| d = 3 s | settles at 6.67 blocks/s, estimate 1.50x, difficulty 1.50x the correct value | 10.00 | 10.00 |
|
||||
| d = 5 s | 4.00 blocks/s, 2.50x | 10.00 | 10.00 |
|
||||
| d = 10 s | 2.00 blocks/s, 5.00x | 10.00 (red 0%, mergeset 100) | 10.00 |
|
||||
| d = 30 s (a saturated hub) | 5.21 blocks/s, red 19.5%, estimate 12.1x, difficulty 1.93x | 10.00, red 58%, mergeset 300 | 10.00 |
|
||||
| 1 bps, k 18, cap 20 s: d = 20 / 30 / 60 s | runs away upward: 179 / 43 / 10 blocks/s, red 97 to 99%, estimate 0.01 to 0.09x (the under-read, main's direction) | 1.00 / 1.00 / 1.17 blocks/s | 1.00 / 1.00 / 1.17 |
|
||||
|
||||
Reading against the record. Run A's production fell from 26 blocks/s to 3.3 to 3.6 while blue stayed 1.4: the DAG hardened. The fork's rule at a chain-step spacing of 5 to 6 s (the hub's queue made chain blocks 10 to 60 s apart at the widest, 3 to 6 s late in the run) settles at target x 2 s / spacing = 3.3 to 4 blocks/s, which is the record's late plateau. The ease direction main reported is the other bias (blue work only) and the model shows it where chain steps stay under the cap while the DAG is wider than k + 1: at 1 bps with d of 20 s or more, or at 10 bps when production exceeds k / (2 d). Either way the rule is reading the wrong quantity: a controller that counts every mergeset block's work over the real span (what Kaspa's `calculate_difficulty_bits` does over its sampled window of blue and red mergeset blocks) holds 10.00 in every cell. A second inconsistency at 10 bps: CAP_BLOCKS 20 is 2 s while FUTURE_TOLERANCE_MS and BACK_TOLERANCE_MS stay 10 s, so a forged stamp (10 s) no longer fits inside half a cap; spec 2.3 derived the 10 s as half the cap at 1 bps. The cap, the tolerances and the 60 T lag bound should be denominated in DAA seconds (20 s, 10 s, 60 s) at every rate, and the step of a chain block that merges m blocks should be allowed m x 20 T before clamping.
|
||||
|
||||
Cost of the bug tonight per tier: a home miner's blocks were 77 percent red (a red inside the DAA window pays its 80% to the merging miner, spec 2.5, so the solo miner lost the subsidy of 3 blocks in 4); a rig the same; a pool user nothing (Devnet 2 is a staging chain); the node operator saw 5.4 GB RSS and 180% CPU on a 96-thread box; a prover saw the exec tip at 154 chain blocks against 11,239 blocks; a holder nothing (no value on Devnet 2).
|
||||
|
||||
### 5.5 Node tiers per block rate (`cost-tables.md` 4 and 5)
|
||||
|
||||
| Rate | Per-block CPU budget (0.8 core) | Class v4 verify share of it (box proxy 4.9 ms; a 2019 laptop core about 2.5x, lane 2's rule: 12 ms) | Votes per block (V/30 if C1 stays in blue blocks) and their BLS cost at V = 1,000 | RSS: caches + DAG store over the 30-h window at 30 / 80 KB per block | Disk per day (blocks, votes at V = 1,000, exec records) | Verdict: laptop 2019-class 8 GB SATA | Raspberry-class 8 GB USB SSD |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 bps | 800 ms | 0.6% (laptop 1.5%) | 33 votes, 50 ms | 0.77 + 3.3 to 8.6 GB | 0.92 GB | runs if the DAG store plateaus under about 4 GB (owed: the 30-h measurement); marginal at 80 KB per block | the same question; CPU fine (class v4 verify under 15 ms, GHOSTDAG at mergeset 1 to 2) |
|
||||
| 10 bps | 80 ms | 6% (laptop 15%) | 33 votes, 50 ms: 62% of the budget on its own | 0.77 + 33 to 86 GB | 9.0 GB | no: the hub's measured 61 ms at a narrow DAG already uses 76% of the budget on a server core; RSS over 8 GB within hours unless the per-block footprint falls 10x | no |
|
||||
| 32 bps | 25 ms | 20% (laptop 48%) | 33 votes, 50 ms: over budget | 0.77 + 104 to 276 GB | 29 GB | no | no |
|
||||
| 100 bps | 8 ms | 61% (laptop 150%) | over budget | 0.77 + 326 to 864 GB | 91 GB | no: the verifier gate alone forbids it | no |
|
||||
|
||||
Pruning changes the disk, not the RSS and CPU: the pruned node keeps the 108,000 DAA-s window (136 MB of headers and bodies at 1 bps, 1.55 GB at 10 bps, `cost-tables.md` 6) plus the UTXO set, the exec state (128 MB measured plus 1.5 KB per chain block) and the finality store's 2,000 indices (KEEP_CHECKPOINTS). What must change for 10 bps is the per-block footprint in RAM (30 to 440 KB measured; Kaspa's 16 GB minimum at 10 bps says their footprint is near 10 KB per block, derived from 16 GB over 1,080,000 blocks, approximate) and the per-block CPU (under 50 ms at mergeset 248 on a laptop core).
|
||||
|
||||
### 5.6 Bandwidth per node per day (`cost-tables.md` 2 and 3)
|
||||
|
||||
| Rate | Blocks (header + body + records) | Relay overhead (degree 8) | Votes at V = 12 / 100 / 1,000 / 8,192, C1 in blue blocks | Certificates at V = 12 / 8,192 | Total at V = 100 | Total at V = 8,192 | Mean Mbit/s at 8,192 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | 57 MB | 31 MB | 10 MB / 81 MB / 809 MB / 6.6 GB | 24 MB / 112 MB | 194 MB | 6.8 GB | 0.63 |
|
||||
| 10 | 649 MB | 311 MB | 97 MB / 809 MB / 8.1 GB / 66 GB | 237 MB / 1.1 GB | 2.0 GB | 68 GB | 6.3 |
|
||||
| 32 | 2.4 GB | 1.0 GB | 311 MB / 2.6 GB / 26 GB / 212 GB | 759 MB / 3.6 GB | 6.8 GB | 219 GB | 20 |
|
||||
| 100 | 9.3 GB | 3.1 GB | 971 MB / 8.1 GB / 81 GB / 663 GB | 2.4 GB / 11 GB | 23 GB | 687 GB | 64 |
|
||||
|
||||
With C1 in DAA seconds the vote column is 81 MB / 809 MB / 6.6 GB per day at every rate; with lane 3's aggregated certificate (1.2 KB per checkpoint) in place of carried votes it is 3.5 MB per day. What carries it: a home connection (10 Mbit/s up, 50 down, approximate) carries 1 bps at any voter count and 10 bps at under 1,000 voters or with C1 in seconds; a Raspberry-class box on ethernet the same, bounded by its CPU not its link; a mobile node is a light client: 3.4 MB per day in checkpoint mode at 1 bps and 1,000 voters, 34 MB at 10 bps if C1 stays in blue blocks (`cost-tables.md` 10). A star hub pays its peer count times the block bytes in upload: run A's seed sent 2.5 MB/s (20 Mbit/s) to 41 peers; the three testnet seeds (cx23, 20 TB per month included, `seed-nodes.md`) would spend 6.5 TB per month each at that rate, inside the allowance and outside good sense; the peer floor of proposal 4 spreads it.
|
||||
|
||||
### 5.7 Votes and the 3.4.2 arithmetic per rate (`cost-tables.md` 2)
|
||||
|
||||
| Rate | Checkpoints per day (C1 in blue blocks) | Votes per block at V = 8,192 (V / 30) | Drain capacity per checkpoint at the cap 48 / 384 | Vote bytes per day at 8,192, single votes | Archival votes per year at 1,000 / 8,192 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 2,880 | 273 | 1,440 / 11,520 | 6.6 GB | 296 GB / 2.4 TB |
|
||||
| 10 | 28,800 | 273 | 1,440 / 11,520 | 66 GB | 3.0 TB / 24 TB |
|
||||
| 32 | 92,160 | 273 | 1,440 / 11,520 | 212 GB | 9.5 TB / 77 TB |
|
||||
| 100 | 288,000 | 273 | 1,440 / 11,520 | 663 GB | 30 TB / 242 TB |
|
||||
|
||||
Because C1 counts blue blocks, the votes per block and the drain capacity per checkpoint are the same at every rate (the 3.4.2 arithmetic holds: 384 per block drains 8,192 in 21.3 blocks), while the bytes per day and the checkpoint cadence scale with bps: 3-s checkpoints at 10 bps, 0.3 s at 100. Tonight at 42 voters and 10 bps the seed's inbound IgneumFinality route (4,096 deep since the fin-route fix) overflowed at 109 to 2,547 drops per peer, and 16 of 47 determinable checkpoints locked; at 1 bps the same voters cost one tenth. Re-denominating C1 and d in DAA seconds (300 blue blocks and 600 at 10 bps) keeps the finality cost, the lock delay (63 s) and the light-client bytes at their 1-bps values across every rate step; it is a one-line spec change and a parameter in the fork.
|
||||
|
||||
### 5.8 Pruning, archival and the snapshot path
|
||||
|
||||
What a pruned node keeps (spec 02 2.1, `cost-tables.md` 6): headers and bodies back to the pruning point (108,000 DAA s, never past the latest certified checkpoint, F3), with their GHOSTDAG and reachability data (the 2x index factor is approximate); the pruning proof (levels of headers, `pruning_proof`); the UTXO set; the finality store's last 2,000 indices; the exec state (`exec-snapshot.bin` 128 MB measured at chain block 141,700, growing 0.9 KB per chain block, plus `ExecState.records` 1.5 KB per chain block until a window bounds it, M30 note); the proof-record window (`RECORD_WINDOW_CHAIN_BLOCKS`). An archival node keeps every block and its finality section: 40 GB per year of blocks at 1 bps (453 GB at 10 bps) plus votes as table 5.7, so the archival cost is the votes, not the blocks, until certificates replace carried votes (1.3 GB per year at 1 bps).
|
||||
|
||||
The p2p snapshot path as shipped in 0.3.14 (`proving.rs` `on_exec_snapshot`, `p2p_snapshot_gate`, read on `exec-sync-0313`): a peer's `ExecSnapshot` (version, chain id, genesis, tip number and hash, state root, records, the account and storage dump, fees, epoch, paid shards) is accepted only when its tip is a chain block known to this node's consensus, its tip is not 0, not below this node's exec restart, above this node's own executed tip, and only while the executor is blocked or has no state; and, when the restart pin is configured, the snapshot's record at the restart block must carry the pinned root (`exec_restart_state_root`, the fourteenth override field). The file is then written as the node's own resume point and served onward. Three things it does not check: the snapshot's state root at its tip against anything the chain commits to; the records between the restart block and the tip; agreement among peers. The poisoning attack: a peer of a blocked node (every node is blocked after a reorg deeper than its ring or after a restart below its retention root, the 6 October incident class) serves a snapshot whose tip is a real chain block above the victim's tip, whose record at the restart block is correct, and whose state at the tip is wrong. The victim loads it, executes forward from a false state, persists it, serves it to its own peers, and from then on refuses every honest proof record (`check_record`: the statement over its own records disagrees) while its own prover's records are refused by the network. Bound: it is a liveness attack on the victim and its downstream peers, not a consensus or a proof break (full nodes execute natively and a proof over the false state is a proof of the wrong statement; a light client verifies proofs against the chain's records, not against a node's state), and it needs the attacker among the victim's peers at the moment it is blocked, which the peer floor makes a 1-in-(peer count) race unless the attacker runs most of the victim's peers (an eclipse). The hardening: accept a snapshot only when its state root at the newest proven segment at or below its tip equals the `post_root` of the proof record the chain carries for that segment (the chain already carries 274-byte records in coinbase payloads, so this is a lookup, not a protocol change), and take the (tip, root) pair from N of M peers (the light client's 3 of 5, spec 10.6) before loading; a snapshot that fails either is refused and the peer is dropped for the session. Then poisoning needs a false proof record in the chain, which needs the proving key and a block that carries it, and the bound is the proof system's. Sizes per tier: the snapshot is the state (128 MB today, growing with accounts), so a home node's recovery is a 128 MB download and a sha256; an archival node's history is the table above; a light client never holds one.
|
||||
|
||||
### 5.9 Finality through this lane's eyes (cross-reference to lane 3)
|
||||
|
||||
Lane 3 (`finality-and-weight.md` 1 and 5.5) refutes the topology hypothesis for tonight's pause on the live devnet: certificates 6824 to 6842 formed while node 1 and the observer were down, the zero-aggregator fallback carried a quarter of the certificates, and the pause began at the checkpoint where signing weight fell to 53.1 percent of the frozen table, which is the two-thirds rule; the fleet's star is around the seed (the finality route entry: "on a Vast box the seed is the only peer"), not the Mac. This lane adds the measured shape of that star under load: 41 pods each with peers=1, every block and every vote through one process at 180 percent of one core, 2.5 MB/s of upload, the IgneumFinality route dropping thousands of messages per peer, 16 of 47 checkpoints locked. Lane 3's hub-cut harness case (its proposal 6) and this lane's peer floor (proposal 4) are one piece of work: the floor is what makes the harness case pass (a node with 4 outbound peers keeps blocks and votes when any one peer dies), and the fleet library is where both land.
|
||||
|
||||
### 5.10 Block rate: payout intervals, subsidy, lock delay and the solo-miner answer (`cost-tables.md` 7 to 9)
|
||||
|
||||
| Rate | Subsidy per block (full ramp), producer's 80% | 4070 (25 MH/s) at 1.16 GH/s / 100 GH/s / 1 TH/s / 10 TH/s | 5090 (128 MH/s) at the same | 8x 4090 rig (459 MH/s) at the same | Lock after a checkpoint if d stays 60 blue |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 31.69 IGN, 25.35 | 46 s / 1.1 h / 11.1 h / 4.6 d | 9.1 s / 13 min / 2.2 h / 21.7 h | 2.5 s / 3.6 min / 36 min / 6.1 h | 63 s |
|
||||
| 10 | 3.17, 2.54 | 4.6 s / 6.7 min / 1.1 h / 11.1 h | 0.9 s / 1.3 min / 13 min / 2.2 h | 0.3 s / 22 s / 3.6 min / 36 min | 9 s |
|
||||
| 32 | 0.99, 0.79 | 1.4 s / 2.1 min / 21 min / 3.5 h | 0.3 s / 24 s / 4.1 min / 41 min | 0.1 s / 6.8 s / 1.1 min / 11 min | 4.9 s |
|
||||
| 100 | 0.32, 0.25 | 0.5 s / 40 s / 6.7 min / 1.1 h | 0.1 s / 7.8 s / 1.3 min / 13 min | 0.0 s / 2.2 s / 22 s / 3.6 min | 3.6 s |
|
||||
|
||||
The solo-miner question: a 4070 at 25 MH/s sees 21.6 blocks a day at 1 bps on a 100 GH/s network (548 IGN a day at full ramp) and 2.16 a day at 1 TH/s (55 IGN); one block a day needs 0.046 bps at 100 GH/s, 0.46 bps at 1 TH/s and 4.6 bps at 10 TH/s. Kaspa's 10 bps buys the solo miner a daily block up to about 20 TH/s; Igneum at 1 bps gives it up to about 2 TH/s, which is 1,700x tonight's hash and USD 562,000 a day of rented hash at the bench entry's USD 281 per GH/s-day. What the rate costs the node, from 5.5 and 5.6: 10x the bytes (2 GB a day at 100 voters), a per-block CPU budget of 80 ms that the measured 61 to 345 ms does not meet, an RSS that leaves 8 GB within hours at the measured footprints, 3-s checkpoints and 66 GB a day of votes at 8,192 voters unless C1 moves to seconds, and the controller's cap inconsistency. Per tier: a home miner on one card gains variance relief it does not yet need (a 4070 at 1 bps sees a block an hour at 100 GH/s); a rig gains nothing (36 min per block at 1 TH/s already); a pool user nothing (pools remove variance); a prover gains nothing and pays the exec follower's 10x chain blocks; a holder gains faster locks (9 s) only if d is left in blue blocks, which costs the finality bytes of 5.7; a rollup customer the same lock question; the node operator pays all of it.
|
||||
|
||||
## 6. Ranked proposals
|
||||
|
||||
| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Block rate: 1 bps for the public testnet and for mainnet launch; 10 bps stays a planned step (spec 2.1) behind three gates: per-block node CPU under 50 ms at mergeset 248 on a 2019 laptop core, C1 / d / the clock cap in DAA seconds, vote aggregation; no 32 or 100 bps step is proposed | 5.2 (reds are node throughput), 5.5 (budget 80 ms against 61 to 345 measured), 5.7 (66 GB a day of votes), 5.10 (1 bps already gives a 4070 a block an hour at 100 GH/s) | `propagation.py` F and G, `cost.py` 4 and 7 | 1 (the spec line) plus the gates below | home miner: no node change, a block an hour at 100 GH/s; rig and pool: unchanged; prover: the exec follower stays at 1 chain block per second; holder and rollup: 63-s locks; node operator: today's node | the three gates pass on Devnet 2 at 10 bps with zero red over the knee, before any rate step |
|
||||
| 2 | Controller: every lane counts every mergeset block's work (blue and red) over the real span; the clock cap, tolerances and lag bound in DAA seconds (20, 10, 60 s) at every rate; a chain step that merges m blocks may span m x 20 T before clamping | 3.1 (26 to 3.5 blocks/s with blue at 1.4), 5.4 (the fork over-reads by spacing / cap and under-reads by (k + 1) / m; Kaspa's window reads the whole DAG) | `controller.py`: 10.00 in every cell for the whole-DAG estimator | 8 (rule and unit tests in `difficulty.rs`, `igneum.rs`) + 3 (the harness case: fast-time 3 nodes, a 3-s proxy delay at 10 bps; pass = DAG rate within 10% of target and difficulty within 10% of the hash-implied value after 10 min, the same at 1 bps with a 30-s delay) | home miner: the subsidy stops tracking the controller's error (tonight 77% of blocks unpaid to their miner); node operator: no runaway DAG from a slow hub; holder: emission on schedule (spec 2.5's "when the controller lets the rate run" clause stops firing) | the harness case passes; `sim/difficulty/sim.py --dag-delay` replay of run A's production curve settles at 10 bps |
|
||||
| 3 | Finality interval and determination in DAA seconds (C1: checkpoint every 30 DAA s; d in DAA s), one carriage per vote (a block carries a vote only if no block in its past does, which is the rule as written; the relay dedups by (key, index)), and lane 3's aggregated certificate as the archival object | 5.7 (votes and route load scale with bps under the blue-block rule), 3.1 (2,547 route drops per peer, 16 of 47 locks) | `cost.py` 2 and 10 | 4 (spec 03 C1, 3.10 table) + 8 (fork: `finality.rs` interval in DAA s, relay dedup) | home miner and rig: finality bytes stay at 81 MB a day at 100 voters whatever the rate; node operator: the route stays inside 4,096; light client: 3.4 MB a day at every rate; holder: 63-s locks at every rate | a Devnet 2 run at 10 bps with 42 voters shows 0 route drops and every determinable checkpoint locked |
|
||||
| 4 | Peer floor and topology: a node reports synced and mines only with at least 4 outbound peers (8 on seeds); the fleet library gives every box the seed plus 2 random boxes; the seed list carries 3 seeds; the hub-cut harness case of lane 3's proposal 6 with the floor as its pass condition | 3.1 (peers=1 on every pod, 2.5 MB/s out of one process), lane 3 section 5.5 ("on a Vast box the seed is the only peer") | `propagation.py` B (mesh of degree 4 and 8 against the star: the same red share at an ideal hub, one fifth of the upload per node, no single queue) | 4 (fleet library and the synced rule in `tools/fleet/lib/`) + 3 (the harness case) | node operator and seed: upload spread 41 ways to 8 ways; home miner: a dead seed no longer stops blocks and votes; finality: votes reach aggregators by two paths | the hub-cut case passes: a lock within 2 checkpoints of the cut, 0 conflicting certificates at the hub's return, every box at 4 or more peers throughout |
|
||||
| 5 | Measure the per-block node cost and its split (lottery verify, BLS per vote, GHOSTDAG and reachability, relay, exec) with `perf` on a Devnet 2 box and on a 2019-class laptop at 1 and 10 bps, at a narrow DAG and at mergeset 248; set the budget rule (cost x bps under 0.8 core) as a CI check on the fast-time network | 3.1 and 5.3 (61 to 345 ms measured as a total only) | section 4.6 | 3 (profile) + 4 (the CI check in `tools/ci`) | every node tier: a number per machine class instead of the hub's; the 8 GB laptop and the Raspberry-class verdicts of 5.5 become measurements | the profile lands in the bench log with per-component ms; the check fails a build whose per-block cost exceeds the budget at the network's rate |
|
||||
| 6 | Snapshot path hardening: a p2p snapshot is accepted only when its root at the newest proven segment at or below its tip equals the chain's proof record for that segment, and (tip, root) agree on 3 of 5 peers; a failing peer is dropped for the session; the gate's unit test gains the two cases | 5.8 (the gate checks tips and the restart pin, never the state at the tip; the 6 October seed loaded a tip-0 snapshot from a fleet box before the gate) | section 5.8 | 6 (fork `proving.rs`, `snapshot.rs`; the record lookup exists in `check_record`) | node operator: recovery from a peer cannot be poisoned below an eclipse; prover: no false-state records from a poisoned node; home miner: the app's node recovers by itself after a deep reorg | `tools/exec-sync/reorg.mjs` gains a poisoned-snapshot case: refused, peer dropped, honest snapshot loaded after it |
|
||||
| 7 | Devnet 2 genesis bits set for the fleet's hash at the run's rate (no 26 blocks/s burst), and the fleet rule: a box mines only after synced with the peer floor (run A's pods mined from genesis for up to 6 minutes before they saw the chain) | 3.1 (the 55-block reorg "unwinding to height 1", the 33% red first phase), 5.2 (the burst alone saturates a 61-ms hub for 5 minutes) | `propagation.py` C and E | 2 (`box-dn2.sh`, `start-seed.sh`, `devnet2-override.json` per rate) | fleet operator: run A2 and run B measure the rate, not the start; every other tier: none | the next Devnet 2 run shows no reorg over 5 in its first 10 minutes and a first-minute production within 2x of target |
|
||||
| 8 | RSS steady state: run a node through a full 30-h pruning window at 1 bps (fast time) and at 10 bps on Devnet 2, read RSS every 10 minutes, bound the DAG store (reachability and GHOSTDAG per block) and `ExecState.records`; decide the 8 GB tier from the plateau | 3.3 (slopes 30, 80, 440 KB per block over short runs; none is a steady state), 5.5 (8 GB is marginal at 1 bps at 80 KB per block) | `cost.py` 5 | 3 (the run) + 8 (the bounds, if the plateau is over 4 GB) | home miner on an 8 GB laptop: a yes or no at 1 bps; Raspberry-class: the same; node operator: a RAM line on the requirements page | RSS plateau under 4 GB at 1 bps over 30 h; the requirements page carries the measured line |
|
||||
| 9 | Node and client tiers published with sizes: full pruned node (1 bps: 0.9 GB a day, 136 MB window plus state, 4 GB RAM target), archival node (40 GB a year of blocks plus 1.3 GB of certificates once aggregated; 296 GB a year of votes until then at 1,000 voters), light client checkpoint mode (3.4 MB a day), phase two (2.3 MB a day) | 5.6, 5.8, `cost-tables.md` 6 and 10 | `cost.py` | 2 (the requirements page and spec 10.5's table at 1 bps, with the rate rows) | every tier knows its cost before the testnet | the page's numbers match `cost-tables.md` and the first testnet week's measured bytes within 30% |
|
||||
| 10 | Bandwidth and route bounds per peer: at most 2 x V / 30 votes per peer per block interval accepted (a second belt behind the dedup), block relay to at most 16 peers per node, the IgneumFinality route sized from V and the rate | 3.1 (2,547 drops per peer), 5.6 | `cost.py` 3 | 4 (fork p2p flows) | seed and node operator: a bound on what one peer can make a node do; home miner: none | the s7 flood scenario gains a vote flood: 0 disconnects, bounded CPU |
|
||||
|
||||
**1. Block rate.** Everything measured tonight says 1 bps is the rate the current node can run and 10 bps is not: the hub spent 61 ms per block at a narrow DAG against a budget of 100, and the model turns that cost into tonight's red share (5.2). Nothing in propagation forbids 10 bps (5.1: zero reds at every measured delay with k 124), so the step stays on the plan (spec 2.1) as Kaspa took it, after a test campaign, with three gates: the per-block cost (proposal 5), the time denomination of finality and the controller (proposals 2 and 3), and vote aggregation (lane 3). The variance argument does not need the step yet: a 4070 sees a block an hour at 100 GH/s and two a day at 1 TH/s at 1 bps (5.10); a pool user never sees variance. Consequences: no node tier changes for the testnet; the testnet gives the measured bytes and RSS that fill proposals 8 and 9.
|
||||
|
||||
**2. Controller.** The rule walks the selected chain and sums blue-work increments over capped clock steps (`igneum_difficulty_bits`, section 4.4). In a wide DAG both parts mislead it: the cap turns a 5-s chain step into 2 s (over-read, harden, the record's fall to 3.5 blocks/s) and the blue-only work hides the reds (under-read, ease, the direction main reported). Kaspa's window pushes every mergeset block, red included, and divides by the real span (`window.rs`, `difficulty.rs`), which the model holds at target in every cell. The change is inside `igneum_difficulty_bits` and `igneum_target`: work = the mergeset's total work per chain step, the step allowed m x 20 T before the cap, the cap and tolerances in DAA seconds. The harness case is the proxy-delayed fast-time network of `sim/difficulty/attacks/README.md` at 10 bps with a 3-s hold, and `sim.py --dag-delay` replaying run A's production curve (the schedule file is in `sim/horizon/network/`).
|
||||
|
||||
**3. Finality interval in seconds.** C1 says 30 blue blocks, d says 60 blue blocks; at 10 bps that is a checkpoint every 3 s and ten times the votes, certificates, route load and light-client bytes per day (5.7), and tonight's seed showed the route overflowing at 42 voters. Written in DAA seconds the whole finality cost is rate-invariant and 3.4.2's arithmetic (384 votes per block drain 8,192 in 21 blocks) gets 10x more headroom at 10 bps. The one-carriage rule is already the spec's text ("not already in its past"); the relay dedup by (key, index) makes the wire match it.
|
||||
|
||||
**4. Peer floor.** Run A's pods had one peer each; the live fleet's boxes have the seed as their only peer; the hub paid 41x the upload and ran one queue for every block and vote. Four outbound peers before a node calls itself synced, the seed plus two random boxes in the fleet library, three seeds in the list: the mesh runs of 5.2 show the same zero red at an ideal hub with one fifth of the per-node upload and no single queue, and lane 3's hub-cut case becomes passable. This is the proposal that serves both lanes.
|
||||
|
||||
**5. The per-block profile.** The 61 to 345 ms is a total; the split decides which fix buys 10 bps: if BLS of carried votes dominates, aggregation and the dedup buy it; if GHOSTDAG at k 124 dominates, the mergeset limit and a cheaper reachability do; if relay dominates, the peer floor does. Three hours with `perf` on a Devnet 2 box, then a CI check that fails a build whose cost exceeds the budget at the network's rate, so the class is closed the way CLAUDE.md asks.
|
||||
|
||||
**6. Snapshot hardening.** The gate refuses tips, not states (5.8). The chain carries the proof records' roots, so a snapshot's root at its newest proven segment can be checked against them without a protocol change, and 3-of-5 peer agreement makes poisoning an eclipse. The attack is a liveness attack on the victim, never a consensus break, but a poisoned seed serving its state onward is the fleet-wide class the 6 October gate was written for.
|
||||
|
||||
**7 to 10.** Devnet 2's genesis bits and the start-synced rule make the next runs measure the rate instead of the start (the 55-block reorg and the first-phase reds were the start); the 30-h RSS run decides the 8 GB tier with a measurement instead of three slopes; the tier page and the per-peer bounds are two hours each and close open rows in spec 10.5 and the finality route entry.
|
||||
|
||||
## 7. Open questions and what could not be run
|
||||
|
||||
| Question | Why not tonight | What closes it |
|
||||
|---|---|---|
|
||||
| Run B (1 bps control) and A2 (mesh) rows | B starts at 20:25Z, rows about 21:00Z; A2 is not scripted | the fleet's RUN_B row; A2 built on `box-dn2.sh` with 3 `--addpeer` entries (seed plus two boxes) and genesis bits for 10 bps; this lane's prediction for A2 is in 5.2 |
|
||||
| The difficulty trajectory of run A | the collector strips `difficulty=`; the node log has no bits | `bps-collect.py` keeps the field; or `getBlockDagInfo` difficulty per minute on the next run |
|
||||
| The per-block CPU split | no profile; the hub's figure is a total that includes relay to 41 peers | proposal 5 |
|
||||
| RSS steady state at 1 and 10 bps | three slopes over 1,500 to 15,000 blocks, none over a pruning window | proposal 8 |
|
||||
| Kaspa's measured 10-bps red rate | not in the clone (`crescendo-guide.md` has requirements only) | the kaspanet KIP-14 text and the Crescendo testnet-10 reports, not cloned; approximate under 1 percent by the k derivation |
|
||||
| BLS verify per vote on this stack | O-10.3 open; 1.5 ms is approximate | `fast_aggregate_verify` timing with the forked `blst` build |
|
||||
| The pods' per-block cost (the A2 prediction's fork) | the pods' CPUs and their node logs were not read (fleet boxes are out of this lane's reach) | the fleet's collector reads `cpu_rss` on every box, not only the seed |
|
||||
| GHOSTDAG's second k-cluster condition | `propagation.py` applies the candidate's own anticone bound only; lane 4's `ghostdag_sim.py` carries both | re-run the grid with lane 4's colouring if a cell is contested (every cell here is 0 percent, so the undercount cannot change the reading) |
|
||||
| The 55-block reorg's cause | read from the log as a late-joining pod's chain from genesis ("unwinding to height 1") | the start-synced rule of proposal 7 removes the class; the next run's reorg distribution confirms |
|
||||
|
||||
## 8. Summary for the coordinator
|
||||
|
||||
Run A at 10 bps did not measure propagation; it measured a node that needs 61 to 345 ms per block through one hub whose budget at that rate is 100 ms. The propagation model with the measured links predicts under 0.1 percent red in the star and in the mesh; with the hub's measured per-block CPU it predicts 47 to 88 percent against the 77.2 percent measured, with the queueing waits the log's 100-block relay batches show, and it predicts that the mesh variant A2 goes red too unless every pod's per-block cost is under 80 ms and the genesis burst is removed. The controller then hardened (production 26 to 3.5 blocks/s with blue at 1.4) because its estimator reads blue work over a chain step capped at 2 s; the whole-DAG estimator Kaspa uses is unbiased in every regime the model covers. The block rate for the public testnet and mainnet launch is 1 bps; 10 bps stays a gated step, and the gates are a per-block cost under 50 ms on a laptop core at mergeset 248, the finality interval and the clock cap in DAA seconds, and vote aggregation. Lane 3's finding stands (the pause was the rule, the fleet's star is around the seed); the peer floor is the piece of work both lanes need.
|
||||
|
||||
1. Reds from node cost, not topology: red share 0.0 percent in every cell of the bps x delay grid (1 to 100 bps, 50 to 1,000 ms hops, k from Kaspa's table) and in the run A replay with an ideal hub; 47 / 88 / 83 percent with the hub at 61 / 115 / 345 ms per block (measured); the knee at 10 bps is 100 ms per block (`sim/horizon/network/propagation.py`, `results.md`, `results-2.md`).
|
||||
2. The controller's two biases: estimate / true hash = (blues / m) x (spacing / cap); at a 5-s chain-step spacing the fork settles the DAG at 4.0 blocks/s against 10 (record 3.3 to 3.6), at a 20-s spacing at 1 bps it runs away to 179 blocks/s; the whole-DAG estimator holds 10.00 and 1.00 in every cell (`controller.py`).
|
||||
3. Finality cost scales with the rate under C1 as written: 8,192 voters cost 6.6 GB a day per node at 1 bps and 66 GB at 10 bps (24 TB a year archival); in DAA seconds 6.6 GB at every rate, 3.5 MB a day with aggregated certificates; tonight's seed dropped up to 2,547 finality messages per peer and locked 16 of 47 checkpoints at 42 voters (`cost.py`, section 3.1).
|
||||
332
docs/analysis/horizon/new-pow.md
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
# Horizon lane 8: a new proof of work (three candidate schemes, reviews, prototypes, verdicts)
|
||||
|
||||
6 October 2026, evening UK, lane `new-proof-of-work`, worktree `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon` from master, at 3f4f719). Output of this lane: this file and `proto-newpow/<scheme>/`. Nothing here touches the shipped hash, `igneum-pow`, the node, the manifest or the live devnet; every prototype is a benchmark beside the worker, never inside it.
|
||||
|
||||
the project lead's mandate, verbatim: "if we create a new way of hashing or a new way of proof of work to revolutionise the space then that's absolutely fine, I want you to deploy everything to create something that has not ever been done before."
|
||||
|
||||
## 0. Progress (kept current for the coordinator)
|
||||
|
||||
| Time (UTC) | State |
|
||||
|---|---|
|
||||
| 19:05 | Lane started. Read: preamble, CLAUDE.md, the two personas, spec 01 (whole), 04, 07, chip-model-v3 (whole), asic-resistance-history (sections 0 to 3 and 4.3, 5), latency-shadow-2026-10-06 (whole), counter-asic-3-status sections 1 to 5, counter-asic-3-node section 6 (the P2 signalling rule), int8-matrix-family sections 1 to 3, scratch-soundness verdict, proving-methods (whole), fud-ledger M1, M7, M16, M22, M28, P2, F13, proto-cuda host.cu and the mx8-genesis pack (kernel.cu, memhard.h, program.h, vectors.h), proto-cuda/emu, family-probe.cu, the fleet's prover-tiers-real-cards.md, bench-log line 2582 (rental cost) |
|
||||
| 19:25 | Fleet agent asked for two boxes; answered at 19:29: two quiet RTX 4090s (RunPod, nvcc 12.8 at /usr/local/cuda/bin, directory /root/horizon-newpow, until 22:30Z). No quiet Ampere card exists tonight; a loaded 3090 is offered. Main's note: cost rows use bench-log 2582 (USD 0.0117 per MH/s-hour) |
|
||||
| 19:35 | File skeleton written. Two prototype sub-agents launched (budget two at once): `mma-shadow` on box 1 (<box-1-ip>), `state-dataset` on box 2 (<box-2-ip>) plus CPU rows on igneum-build-1. Designs being written in this file meanwhile |
|
||||
| 19:41 | Section 3 complete: the three designs, the one-table comparison, the migration path. Scheme A's verdict is already visible in its own numbers (A1 dead on 2.9 MB of openings per block, A2 dead on sampleability and a 32 to 40 ms proof verify; A0 is scheme C with the trace as state). Prototypes running: `mma-shadow` (box 1) and `state-dataset` (box 2 and igneum-build-1). mm8 two-output correction sent to the prototype |
|
||||
| 19:50 | Section 4 (reviews, the pick: B and C), section 7 (ranked next steps) and section 8 (open questions) written. Scheme C prototype complete and measured on box 2 and igneum-build-1: hash rate and watts unchanged (63.08 vs 63.09 MH/s, 207 W), build +1.4 ms, verifier +0.11 to 0.21 ms per unit, bit-exact 1,024 of 1,024 items and 128 of 128 lanes; rows in 5.2. Scheme B ladder running on box 1 (R = 0, 8, 32, 128 measured, 512 in progress) |
|
||||
| 20:20 | Scheme B ladder complete on box 1 (R = 0 to 512, rate flat at 63.08 MH/s, 201 to 216 W, every fingerprint PTX = reference, 1,024 of 1,024 lanes at every R, verifier delta 0.05 to 4.39 ms). Sections 5.1, 5.3, 6 and 9 written. Box 2 released 19:53Z; box 1 released on the mma agent's report. File complete |
|
||||
|
||||
## 1. What was read and the facts this lane stands on
|
||||
|
||||
Every figure below is from the named file; "approximate" marks a figure from memory.
|
||||
|
||||
| Fact | Value | Source |
|
||||
|---|---|---|
|
||||
| The shipped hash | 64 instructions x 8 iterations, 16 loads per program (128 dependent 4-byte reads per hash), 8 registers, 32-lane unit with xor shuffles, class v3 = mixer x8 item derivation over a 256 MiB ChaCha12 cache, 1 GiB dataset in the packs (2 GiB designed), era draws, VDF seeds | `docs/spec/01-lottery-hash.md` 1.4 to 1.13 |
|
||||
| Verifier today | 2.06 ms per unit on one M5 Max core (class v3, 4,096 item derivations), about 5.2 ms on a 2019-class core by the 2.5x rule; the 10 ms gate | `docs/plans/counter-asic-3-status.md` section 3, `latency-shadow-2026-10-06.md` section 4 |
|
||||
| RTX 5090 at the hash | 136.1 MH/s (readwidth), 132.2 (shadow control), 290 W in the app, 350 W in the bench; 2.34 to 2.65 microjoules per hash; 17.5 G dependent reads per second, 82 percent of the GDDR7 activate ceiling; 45.2 T int op/s; marginal ALU energy 10 to 13 pJ per counted op | `chip-model-v3.md` 5.1, `latency-shadow-2026-10-06.md` 5 |
|
||||
| The chip that matters | the f = 1 stored-dataset memory-controller chip: 5.1x per joule on GDDR7, 7.5x to 9.2x on HBM3 in the model; 2.1x to 4.8x by the Ethash precedent; the recompute chip (f = 0) 0.31x per chip, 1.86x per joule | `chip-model-v3.md` 5.4 to 5.6 |
|
||||
| The one lever against it | program work in the latency shadow: at N = 100,000 ops per hash the chip's edge over the 5090 falls from 5.6x to 2.1x at k = 1 (chip core energy per op equal to the GPU's 11 pJ), to 3.2x at k = 0.5, 4.1x at k = 0.3; class v4 candidate `mx8+sh256x27` | `latency-shadow-2026-10-06.md` 6 and 10 |
|
||||
| Step costs per family on the 5090 (ratio to the add-xor-rotate chain, 7,941 G lane-steps/s) | rotr 1.32, shflx 1.49, shfla 1.53, dot4 1.16, mm8 (`mma.m8n8k16.u8`, bit-exact) 2.43 | `counter-asic-3-status.md` section 3, item 6 |
|
||||
| mm8 on other vendors | AMD RDNA 4: WMMA iu8 builtin reaches gfx12, 1.68 to 1.83 per step, fragment layout UNVERIFIED (exactness not attempted); Apple: no integer simdgroup matrix in MSL, Metal 4 `matmul2d` uchar x uchar into int exists but not from the Swift toolchain used here, per-lane dot4 emulation 1.6x unsigned, 4.7x signed | `counter-asic-3-status.md` item 6 AMD column, `int8-matrix-family.md` 1 and 4 |
|
||||
| Proving today | SP1 6.8.1 Hypercube; the v1 shard (4.7 M cycles) proves in 4.8 to 18 s on 12 to 32 GB cards with the patched server, 7.4 to 8.0 GB alone; compressed proof 1.27 MB, verified in 32 to 40 ms; the aggregator 2.2 to 9.7 s per block | `prover-tiers-real-cards.md`, `proving-methods.md` 1 and 2.1 |
|
||||
| Proof payment | 80/20 lottery/proving split of the subsidy; shards by weighted sortition (8 assignees, 10 s window), aggregator share 1,000 bps, unproven deadline 600 DAA s; the native-execution veto: a record whose statement differs from the node's own execution pays nothing | `docs/spec/07-execution.md` 7.2, 7.7, 7.8 |
|
||||
| Class activation | P2: a class flips when 95 percent of blue blocks over a one-day window carry the object byte (header version high byte), with a fixed-height floor; one-sweep binary rollout; Devnet 2 gate first | `docs/plans/counter-asic-3-node.md` section 6, CLAUDE.md 6 Oct rules |
|
||||
| Rented hash | USD 0.0117 per MH/s-hour (1,748 MH/s for USD 20.44 per hour on RunPod community pods, 18:45Z); the live devnet 1.16 GH/s | `docs/bench-log.md` line 2582 |
|
||||
|
||||
## 2. Method
|
||||
|
||||
Designs first (section 3), each reviewed in two personas (section 4), two picked, two prototypes measured on real cards (section 5), verdicts (section 6). Prototype shape: the mx8-genesis pack's own kernel text (`proto-cuda/packs-ca2-mixer/mx8-genesis/kernel.cu`, `memhard.h`) modified by the smallest change each scheme needs, compiled with nvcc 12.8 on the two RunPod 4090 boxes, timed by CUDA events over 2^24-nonce batches with `nvidia-smi` at 1 Hz, and checked bit for bit against a C CPU reference that interprets a 32-lane unit register-major with lazy item derivation (the verifier's shape) on 1,024 random lanes. CPU rows on igneum-build-1 (EPYC 9454P, Zen 4, one core at up to 3.8 GHz, which is NOT a 2019 core; the 2.5x rule of the project stands in for that core, labelled). Chip rows are the chip-model-v3 method (section 5 of that file) applied to each scheme, approximate where that file is approximate.
|
||||
|
||||
## 3. The three candidate schemes
|
||||
|
||||
Shared notation: `K_d` the day key (spec 1.8.1), `S_e` the epoch seed words (spec 1.3), the unit = 32 aligned nonces (spec 1.9), `item(t)` the 16-word class v3 derivation of spec 1.8.5, `target64` as spec 1.10. Difficulty in every scheme is the unchanged 64-bit target comparison on the unchanged DAA (spec 2.3): none of the three changes what a block's work unit is worth, only what the unit of work consists of, so the difficulty controller sees the same statistics. Each scheme is a program class in the sense of spec 1.4.5 (a `generator` number and a class byte), so activation runs through P2 (section 3.5).
|
||||
|
||||
### 3.1 Scheme A: mining is proving ("proof of committed trace")
|
||||
|
||||
**The claim to test.** The lottery's work is a bounded piece of the chain's own proving, so the 80/20 split collapses into one payment and the hash rate is the proving capacity.
|
||||
|
||||
**The puzzle, in its most favourable form.** Every full node already runs the segment natively (spec 7, the native-execution veto). Add one step to that: every node also runs the zkVM executor (SP1's RISC-V executor, CPU, no proving) on the segment's shard inputs and keeps the shard TRACE: the cells of every table the shard touched, about 280 M cells for the adopted v1 shard, 1.1 GB (`proving-methods.md` 1.4). The lottery of epoch `e` then uses as its dataset the trace of the last segment whose last chain block has DAA score at most `3,600 e - 1,200` (the same 20-minute lead as the epoch seed, spec 4.3), serialised row-major and padded by zero to the dataset size, and the item derivation becomes `item_A(t) = class v3 derivation with s[i] ^= row(t)[i]` for the 16 words of trace row `t` (64 bytes of trace per item). Everything else is the shipped hash: 128 dependent 4-byte reads, the 32-lane unit, the fold, `target64`. Header commitment: nothing new; the trace is a function of the segment, the segment of the chain, the chain of the header's past, exactly as the epoch seed is (spec 4.3 item 4); the class byte 5 of P2 marks the object. Difficulty: unchanged. Verifier: the node derives up to 4,096 items per unit from the 256 MiB cache (as today) plus 4,096 reads of the trace rows it holds in RAM (1.1 GB): the measured cost of exactly this read pattern is scheme C's row in section 5 (the two schemes share the verifier shape). Under 10 ms on a 2019 core if scheme C's row is.
|
||||
|
||||
**Two stronger forms, and why each dies on a number.**
|
||||
|
||||
| Form | What the miner must hold or do | Verifier | Why it dies |
|
||||
|---|---|---|---|
|
||||
| A1, "proof of committed codeword": the dataset is the Reed-Solomon codeword of the trace (BaseFold's stacked encoding at blowup 4, 4.5 GB, `proving-methods.md` 1.1 and 1.4), whose Merkle root the shard record already publishes; the miner must have done the COMMIT stage of the proof (encode and hash) to mine | the LDE and the Merkle tree: the first stage of every STARK or BaseFold prover, the part a GPU spends a large share of its proving time on (approximate: 30 to 50 percent of the stage time, unmeasured here) | cannot derive a codeword word locally: one evaluation of a stacked column at one point is O(2^21) field operations (the stacking height, `proving-methods.md` 1.1), so 4,096 words per unit is about 8 G operations, about 1 s on a core, 100x over the gate. The block must therefore carry Merkle openings: 4,096 per unit (every lane's loads feed every other lane through `shfl`) x 22 levels x 32 bytes = 2.9 MB per block; with a 1-lane unit (no `shfl`) 128 x 22 x 32 = 90 KB per block, 7.8 GB per day of PoW witness at one block a second against about 200 B per header today (Kaspa header, approximate). Headers-first validation, the pruning proof and the light client (spec 10) all break on the bytes |
|
||||
| A2, "mine a proving step": each nonce selects a random piece of the pending proof work (a FRI fold of one column, a Poseidon2 Merkle layer, a zerocheck round) and the hash is of that piece's output | that piece | the verifier either recomputes the piece (then the verifier did the useful work, and the piece bought nothing: the definition of useless) or verifies a proof of it (an SP1 compressed proof verifies in 32 to 40 ms, `proving-methods.md` 2.1, four times the whole gate, and a per-piece proof does not exist). And the pieces run out: a segment's proof is about 5 s of one 5090 (`prover-tiers-real-cards.md`: 4.8 to 18 s per shard on 12 to 32 GB cards, 2.2 to 9.7 s of aggregation) against 8 G hashes per 8-s segment at 1 GH/s; the useful fraction of the lottery's work is bounded by (proving work per segment) / (network hashes per segment), about 8 percent at 1 GH/s and 0.08 percent at 100 GH/s (arithmetic on the cited figures), because proving work is set by gas and lottery work by the security budget. They are the same quantity only by coincidence at one network size |
|
||||
|
||||
**The sampleability objection, stated and answered.** Ball, Rosen, Sabin and Vasudevan, "Proofs of Useful Work" (ePrint 2017/203) build PoUW for problems with a random self-reduction (orthogonal vectors, 3SUM, all-pairs shortest paths): a useful instance is embedded into a random challenge so that the challenge is hard on average, solving it solves the instance, and verification is fast; the price is a polynomial blow-up and a problem class with that structure. Ofelimos (Fitzi, Kiayias, Panagiotakos, Russell, CRYPTO 2022) makes the useful work a doubly-parallel local search whose QUALITY improves with more work, so more hash rate yields better solutions. Primecoin (2013) mined Cunningham chains (useless, but sampleable: the nonce picks the chain's origin). Gridcoin pays BOINC credit through a trusted whitelist, not a puzzle (approximate, from memory for the last two). Proof generation has none of the three properties these constructions need: (1) a segment has one proof, not a distribution of instances, so there is nothing for the nonce to sample; (2) partial progress has no verifiable value under the gate without a proof, and a proof verify costs 32 to 40 ms; (3) the quantity of useful work is fixed by demand (gas), the quantity of lottery work by the security budget (hash rate), and a puzzle whose per-solution work is fixed by demand is not a difficulty-adjustable lottery. The answer this design gives is therefore no: the only form that survives the gate and the bytes is A0 above, in which the miner must HOLD the trace, which is "proof of stored state" with the trace as the state, and the useful work gained per hash is zero (every node computes the trace natively anyway; ledger F13). Mining does not become proving; it becomes proof that the miner executes.
|
||||
|
||||
**Chip edge per joule (chip-model-v3 method).** A0 changes the dataset's contents, not its size or read pattern; the f = 1 stored-dataset chip stores whatever the items are: 5.1x per joule on GDDR7, 7.5x to 9.2x on HBM3, unchanged from `chip-model-v3.md` 5.4. The f = 0 recompute chip must now hold the 1.1 GB trace somewhere (it cannot derive an item without row `t`), so it needs DRAM beside its SRAM and becomes an f = 1 chip; the 0.31x row disappears, which is a small gain since that chip was never the threat. A1 and A2 are not priced: they fail before a chip is drawn.
|
||||
|
||||
**Verifier cost.** A0: the class v3 derivation (2.06 ms per unit on an M5 Max core, measured) plus 4,096 random 64-byte reads from a 1.1 GB array; the read row is measured in section 5 (scheme C's CPU rows, the same pattern). A1: 2.9 MB of openings or a 1-lane unit (see table). A2: a proof verify (32 to 40 ms, measured) or nothing useful.
|
||||
|
||||
**Bit-exactness.** A0: the trace rows are bytes; the derivation is integer; nothing vendor-specific is added. The zkVM executor's trace must itself be deterministic across platforms (SP1's executor is Rust, no floating point in the trace path, approximate: not audited here); any disagreement about a trace cell is a consensus split, so A0 adds the SP1 executor to the consensus-critical code, which ledger P7 already names as a risk class for the proofs and which here would extend to the lottery.
|
||||
|
||||
**Known attacks.** Grinding: a block producer influences the trace through the transactions it includes, but the trace used is 20 minutes old and keyed by `K_d` through the mixer; a producer cannot predict a useful bias in 128 dependent reads of a keyed derivation (the MTP lesson, `asic-resistance-history.md` 2.3: Dinur and Nadler controlled addresses by controlling contents; here the contents enter only through a keyed, chained derivation whose addresses are the cache-line indices of the mixer state, not the trace). Outsourcing: pools serve the dataset (1 to 2 GiB per miner per day), so "the miner holds the trace" becomes "someone in the pool holds it". Precomputation: the dataset is computable 20 minutes early, as today. Light evaluation: as the f = 0 row above. Sampleability: the paragraph above. Empty segments: a day or an epoch with no transactions has a near-empty trace (the shard statement still applies rewards, spec 7.7 item 8), so the dataset is mostly padding and the hash degrades to today's class v3: graceful, not an attack.
|
||||
|
||||
**Game theory of the collapsed payment (if A1 or A2 had worked).** The 20 percent pool would go: provers are miners and the proof is the by-product of the lottery. Who is paid for what: the block reward pays for the block and the proof piece in it; a prover that mines earns exactly a miner that proves. Pools: the pool does the useful work and sells shares, as today, so proving centralises exactly as mining does (Aleo's proof of succinct work centralised on the same path: the coinbase puzzle was a synthetic circuit proven by whoever had the most GPUs; approximate, from memory, the cryptographer persona's own list). Proving demand at zero: the puzzle has no useful content and must fall back to a synthetic dataset, so the chain carries two puzzle modes and a mode switch, a consensus rule and a grinding surface. External jobs: a customer's proof could only be mined if its segment entered the puzzle, so the security budget would be spent on the customer's work at the marginal cost of inclusion, a subsidy from holders to customers unless priced by a burned fee. These are the reasons CLAUDE.md records the lottery and the proving as separate on purpose; nothing found tonight overturns them.
|
||||
|
||||
**Migration.** A0 is a class byte (P2): class v5 by the 95 percent signal with the floor height, one-sweep binary rollout, Devnet 2 gate first (CLAUDE.md 6 Oct). Every node must run the zkVM executor on every segment before the flip (CPU cost: the v1 shard is 4.7 M cycles; SP1's executor runs at tens of MHz on a CPU, approximate, so under a second per 8-s segment); a node without it cannot validate PoW after the flip, which is what the floor height is for.
|
||||
|
||||
**Verdict line (section 6 has the reasoning):** NEVER as "mining is proving" (A1, A2); A0 is scheme C with the trace as the state and is folded into C.
|
||||
|
||||
### 3.2 Scheme B: a GPU-structure-bound puzzle ("mx8+mm8xR", the tensor-shaped integer shadow)
|
||||
|
||||
**The claim to test.** Fill the latency shadow (the only lever that moves the f = 1 chip, `chip-model-v3.md` 5.7, `latency-shadow-2026-10-06.md`) with work shaped like a GPU's own tensor datapath rather than its scalar ALUs, so that a chip must carry GPU-class matrix units whose energy per operation NVIDIA's own silicon already sits near the floor of, and the chip's residual edge `k` (its energy per op divided by the GPU's) cannot fall far under 1. The brief's other candidates are rejected on definition: shared-memory bank timing and register-file width are timings and capacities, not values, and a consensus rule can only check values; a per-warp scratchpad was measured and found not sound as a chip layer (`docs/analysis/scratch-soundness.md`: the live state is bounded by the read-modify-write count, 64 to 320 bytes per lane, which a chip keeps in SRAM at under 5 percent of its mirror).
|
||||
|
||||
**The puzzle.** Class v3 (mixer x8, 16 loads, the 64 base instructions, the era draws) plus a block of `R` `mm8` steps executed at the end of every iteration, after instruction 63 and before the next iteration samples `sel`; `8 R` steps per hash, no load in the block, the base program untouched (the same seam the class v4 shadow uses, `latency-shadow-2026-10-06.md` section 2, so the acceptance rule of 1.4.6 keeps its verdict draw for draw). Step `k` has four draws from the program stream after the base and shadow draws: `a = below(8)`, `b = below(7) + (b >= a)`, `c = below(8)`, `c2 = below(7) + (c2 >= c)`. Its semantics over the unit:
|
||||
|
||||
```
|
||||
A: 8 x 16 uint8 lane l holds A[l >> 2][4 (l & 3) .. 4 (l & 3) + 3] = the 4 bytes of r[a] (byte 0 = lowest k)
|
||||
B: 16 x 8 uint8 lane l holds B[4 (l & 3) .. +3][l >> 2] = the 4 bytes of r[b]
|
||||
C = A x B C[i][j] = sum over k of A[i][k] B[k][j], exact in int32 (at most 1,040,400)
|
||||
r[c] = r[c] + C[l >> 2][2 (l & 3)] (mod 2^32)
|
||||
r[c2] = r[c2] + C[l >> 2][2 (l & 3) + 1] (mod 2^32)
|
||||
```
|
||||
|
||||
This is the fragment layout of PTX `mma.sync.aligned.m8n8k16.row.col.s32.u8.u8.s32` with the two `.s32` outputs `d0`, `d1` of each lane (PTX ISA, "Matrix Fragments for mma.m8n8k16", the integer layout; `docs/analysis/int8-matrix-family.md` 2.2 quotes it and `proto-cuda/family-probe.cu` carries the CPU reference that was bit-exact on the RTX 5090 on 5 October 2026, `counter-asic-3-status.md` item 6). The spec defines the matrices and the lane ownership, not the instruction: a vendor permutes its own fragment layout into this one (a shuffle) and the result is defined whatever the hardware. Why TWO destinations where the reserve entry R8 of 1.13.2 has one: with one element per lane only 32 of the 64 products of C are consumed, so a chip does 512 multiply-adds per step where the GPU does 1,024 and the GPU hands the chip a free 2x on the block; with both outputs consumed the whole tile is load-bearing. This is a correction to the reserve text as well (section 7). Unit of work: still the unit. Header, difficulty: unchanged; the class byte 5.
|
||||
|
||||
**Parameters and the verifier's law.** Per unit the verifier adds `8 R x 1,024` unsigned byte multiply-adds (the register-major interpreter already holds the 32 lanes' registers, so A and B are in hand). Plain scalar C: about 2 ops per multiply-add, 16,400 ops per step per unit; at the 18 G op/s the x8 verifier shows on the M5 Max core (`chip-model-v3.md` 5.7, measured) that is 0.9 microseconds per step per unit: `R = 128` adds about 0.9 ms, `R = 512` about 3.7 ms. With a byte-dot instruction (AVX-VNNI `vpdpbusd`, 64 multiply-adds per instruction; NEON `udot`, 16 per instruction) the same work is 4x to 16x fewer instructions (approximate). The headroom is the class v3 margin: 7.9 ms steady on the M5 Max core, 4.6 ms on a 2019-class core by the 2.5x rule (`latency-shadow-2026-10-06.md` section 4), so scalar `R` is bounded near 500 on the 2019 core and the measured rows of section 5 set it. On the GPU side the 5090's dependent-chain probe runs `mm8` at 2.43x the add-xor-rotate step, 7,941 / 2.43 G lane-steps per second = 102 G `mm8` per second per card, 104 T multiply-adds per second (the chain probe, not the tensor peak); at 4.25 M units per second (136 MH/s) the card could hide about 24,000 `mm8` per hash before the probe rate binds, `R` about 3,000, far above what the verifier allows. So the verifier binds first, at about `R = 500` scalar on a 2019 core (approximate until section 5) and perhaps 4x higher with VNNI: the honest card never leaves the latency bound on the candidate `R`.
|
||||
|
||||
**Chip edge per joule (chip-model-v3 method).** The f = 1 chip (memory, controller, static: 0.466 microjoules per hash on GDDR7, 0.321 on one HBM3 stack, `chip-model-v3.md` 5.4) plus a tensor array: energy per hash = memory + `8 R x 1,024 x e_mac x k_mma`, where `e_mac` is the honest card's marginal energy per multiply-add at the block (measured in section 5 as watts delta over MACs per second) and `k_mma` the chip's ratio to it. The difference from the ALU shadow (`k` down to about 0.3 for a fixed-datapath array at N5, `latency-shadow-2026-10-06.md` 6) is where the honest card's engine sits: NVIDIA's tensor cores are int8 multiply-add arrays at N4/N5-class density already, so a chip's array is the same circuit (approximate: int8 MAC datapath about 0.05 to 0.1 pJ at N5, the movement of fragments through the register file the larger term on both sides; from memory) and `k_mma` is near 1 with a floor near 0.5 for a chip that keeps the fragments in a local register file instead of the GPU's banked one. Rows at `k_mma` = 1, 0.5 and the measured `e_mac` are filled in section 5.3 from the 4090 measurement; the shape of the result is already clear: the block lowers the chip's edge by the same mechanism as the ALU shadow and the chip's best case is better bounded, at the price that the honest card's own watts rise by the block's energy (the tensor path is efficient, so the rise per unit of chip-forcing work should be smaller than the ALU shadow's 11 pJ per op; the measurement says).
|
||||
|
||||
**Bit-exactness across vendors.**
|
||||
|
||||
| Vendor | Path | State |
|
||||
|---|---|---|
|
||||
| NVIDIA sm_75 and later (Turing, Ampere, Ada, Blackwell) | one `mma.sync.m8n8k16.u8` per step, identity permutation, wrap on the `.s32` accumulate (the wrap edge vector of the R8 entry was bit-exact on the 5090, `int8-matrix-family.md` 3) | measured bit-exact on the 5090 (5 October) and on the 4090 tonight (section 5) |
|
||||
| NVIDIA sm_61 to sm_72 (Pascal GTX 10 series, Volta) | no `mma` with `.u8`; emulation: 8 `__shfl_sync` gathers plus 8 `dp4a` per lane per step (`dp4a.u32.u32`, sm_61+) | unmeasured; cost about 16 steps per `mm8` against 2.43 native, approximate; within the 8x emulation bound of 1.13.2 on a per-op basis only if the shuffles are cheap; a GTX 1080 owner is the first NVIDIA tier to pay |
|
||||
| AMD RDNA 3 and 4 (gfx11, gfx12) | `V_WMMA_I32_16X16X16_IU8` with the 8 x 16 and 16 x 8 tiles zero-padded to 16 x 16 and a fixed lane permutation (`ds_bpermute`) into the spec layout; the RDNA 4 builtin takes 2 ints per lane for A and B (`int8-matrix-family.md` 1) | the fragment layout is UNVERIFIED (status item 6: not in any source at hand; the CPU reference was not attempted rather than guessed). This is the gate for AMD: a PC 1 job on the 9070 XT with the spec reference. Until it passes, AMD runs the emulation path (`v_dot4_u32_u8` is native on gfx11 and gfx12, 1.06x per op measured) at about 12 to 16 steps per `mm8`, approximate |
|
||||
| AMD RDNA 2 and older, CDNA | no WMMA on RDNA 2; `v_dot4_i32_i8` exists (`dot1-insts`, signed only, approximate); CDNA 3 has `V_MFMA_I32_16X16X32_I8` with its own layout | emulation; unmeasured |
|
||||
| Apple (M-series, Metal) | no integer `simdgroup_matrix` in MSL; Metal 4 `mpp::tensor_ops::matmul2d` has `uchar x uchar -> int` (table 7.3, OS 26.4) through a tensor API not reachable from the Swift toolchain this project uses, and its wrap semantics are unverified (`int8-matrix-family.md` 1). The emulation: 4 shuffles plus 4 unsigned `dot4` emulations at 1.6x per op (measured 5 October), about 10 ALU steps per `mm8` | an Apple miner pays about 4x NVIDIA's per-step cost on the block (approximate); the M5 Max is latency-bound to about 130,000 counted ops per hash (measured), so `R = 128` (1,024 `mm8` per hash, about 10,000 steps emulated) fits inside its shadow and `R = 512` (about 41,000) still does by the ops count, with the hash-rate cost owed to a Metal measurement. Apple's path is the honest card's worst and the chip's argument does not depend on it |
|
||||
| Intel Arc | XMX through `cl_intel_subgroup_matrix_multiply_accumulate` (approximate, unverified); dp4a-class `dot` otherwise | unmeasured |
|
||||
|
||||
So the fleet splits by generation: Turing-and-later NVIDIA and RDNA 3-and-later AMD run the block natively; everything older and Apple emulate. The 5 percent rule (`counter-asic-2-public.md`) is checked per card with the block live, as 1.13.2 requires for an emulating vendor.
|
||||
|
||||
**Known attacks.** Grinding: none new; the block has no data-dependent control flow and reads no memory. Outsourcing, precomputation: as today (the draws are public per epoch; there is nothing to precompute because the inputs are the per-nonce registers). Light evaluation (the f = 0 chip): untouched; the block never reads the dataset. MTP-class content control: not applicable (no attacker-chosen memory). Sampleability: not applicable. New: (i) the half-tile shortcut, closed by the two-destination form above; (ii) a zero or low-entropy fragment (if `r[a]` is zero in every lane the step is free): the acceptance rule's register-saturation test (1.4.6 (c)) already rejects programs with stuck registers, and the base program's loads re-randomise every register every iteration; (iii) the trailing-step contraction: the last `mm8` of the last iteration writes two registers that feed only the fold; a chip could skip nothing because the fold reads all eight, but a step whose destinations are both never read again before the fold still costs the GPU a full tile; the draw rule should forbid `c, c2` outside the fold's register set, which is every register, so there is no such step. (iv) The licensable-IP objection (the history's reason for ranking `mm8` last in the reserve, `asic-resistance-history.md` 4.3 row 6): a chip maker licenses an int8 MMA block at any node. True, and it is the point of the design: the chip must then carry a GPU-class tensor array per 32 lanes in flight at the memory's activate ceiling, 1,172 lanes on GDDR7 (`chip-model-v3.md` 5.5), 37 tiles in flight, and its edge is `k_mma`, a ratio of two copies of the same circuit; the design does not claim the chip cannot be built, it claims the chip is a GPU.
|
||||
|
||||
**Game theory.** None of the payment changes; this is a hash change. A pool user sees nothing. A prover that mines pays the block's watts on the same card it proves on; the tensor path is also the proving path's (SP1's Poseidon2 and NTT kernels are integer, not tensor, so there is no contention beyond the power limit, approximate). When proving demand is zero nothing changes.
|
||||
|
||||
**Migration.** Class v5 by P2: the object byte 5, the 95 percent one-day window, the floor height; one-sweep binary rollout because the digest flips; Devnet 2 gate first. The kernel emitter (`igneum-pow/src/emit.rs`) gains the block in its three dialects, the CUDA one with inline PTX and the `IGNEUM_MM8_REF` fallback for sm_61 to sm_72, the OpenCL one with the AMD builtin behind a feature test and the emulation otherwise, the Metal one with the emulation; the one-click workers compile the text as they do today (NVRTC accepts inline PTX). Gates before a cut: the six gates of `counter-asic-2-rollout.md` section 7 plus the AMD layout verification and the Metal emulation's hash-rate cost on the M5 Max.
|
||||
|
||||
### 3.3 Scheme C: proof of stored state ("sd1", the dataset is the chain)
|
||||
|
||||
**The claim to test.** The dataset is the recent chain state, so every hash proves the miner holds the chain, and the lottery's reads double as a verifiable random sample of state for light clients.
|
||||
|
||||
**The puzzle.** Day `d`'s snapshot `SS(d)` is the execution state at the state root `R_d` of the certified checkpoint `C_day(d)`, the highest-index checkpoint whose block has DAA score at most `86,400 d - 1,200` (the epoch seed's lead, spec 4.3). Its leaves: the `(key, value)` pairs of the execution state trie in key order (storage slots, account records, 64-byte chunks of code), leaf `t` serialised as `leaf(t) = Blake2b-512(R_d || t_le32 || key_t || value_t)` (the chain's own hash, spec 0.6), 64 bytes each, so every leaf carries full entropy whatever its content and no two days share a leaf; for `t` beyond the state's leaf count `leaf(t) = 0`. When the state has more leaves than the dataset has items, the dataset holds the first `2^(D-4)` leaves in the order of `Blake2b-256(K_d || key)`: a keyed sample that cannot be chosen without the whole state. The item derivation is class v3's with one line added before the round loop: `s[i] ^= leaf(t)[i]` for `i` in 0..15. The hash kernel is byte for byte the shipped one; only the daily build changes. Header commitment: none new. `R_d` is the state root of a block in the header's own past at a fixed blue score, so "which snapshot was this block mined under" is a function of the header alone once the chain is known, as the program is (spec 1.12). The class byte 5 marks the object. Difficulty: unchanged. Unit of work: unchanged.
|
||||
|
||||
**The verifier.** A node holds the 256 MiB cache (as today) and `SS(d)` in RAM or mmap (2 GiB at the genesis dataset size, growing on the 1.13.3 schedule), derives up to 4,096 items per unit lazily as today and reads `leaf(t)` for each: 4,096 random 64-byte reads. The measured cost of those reads and of the derivation is section 5.2. Verifier memory: +2 GiB (+4 GiB at year 4). The daily snapshot build: one pass over the state trie, hashed per leaf (one Blake2b-512 per 64 bytes: about 2^25 hashes for 2 GiB, seconds on a core, section 5.2 measures the stand-in).
|
||||
|
||||
**What is new, and the prior art.** Permacoin (Miller, Juels, Shi, Parno, Katz, IEEE S&P 2014) made the puzzle a proof of retrievability over a large PUBLIC FILE chosen by a dealer, with Merkle openings in each block; the file was external to the chain and the openings were the bytes. Spacemesh (proof of space-time over a plotted file of random data) and Chia (Abusalah, Alwen, Cohen, Khilko, Pietrzak, Reyzin, "Beyond Hellman's time-memory trade-offs with applications to proofs of space", ASIACRYPT 2017; Chia's plots) prove storage of USELESS data. Verthash (Vertcoin, January 2021; `asic-resistance-history.md` row 8) is the nearest: a 1.2 GB file generated from the chain's own block headers, random reads, no chip after 69 months on a small prize; its data is headers (low entropy per byte, static once written) and it proves nothing about state. Ethash's DAG is from the epoch seed (random). What Igneum C adds: (1) the dataset is the EXECUTION STATE, keyed per day by `K_d` through the memory-hard derivation, so it is never easier than today's dataset (the leaf is one more 64-byte input to a 9,360-op chain) and it cannot be built from the day key alone: whoever builds it holds the state; (2) every block's 128 loads per lane name 128 keyed items whose leaves are a uniformly random sample of state (the addresses are the mixer-state cache-line indices through 128 dependent reads, unbiasable by the producer at a cost below a block), so a light client that asks any full node for the block's `(key, value)` leaves with their openings against `R_d` gets a free daily spot check of state availability; (3) the beacon: the lottery output is already public randomness, biasable by withholding at the cost of a block, as every PoW; C adds nothing there and the design says so. Honest limit: a pool can ship the 2 GiB dataset or the snapshot to its miners once a day (2 GiB per miner per day, 23 MB/s for a thousand miners), so "every miner holds the chain" is really "every mining OPERATION holds the state", which is still a change: today a pool miner needs nothing but the day key.
|
||||
|
||||
**Chip edge per joule.** Unchanged against the f = 1 chip (it stores items whatever they are): 5.1x on GDDR7, 7.5x to 9.2x on HBM3 in the model, 2.1x to 4.8x by the Ethash precedent. The f = 0 recompute chip must hold the leaves (2 GiB) in DRAM to derive anything, so it becomes an f = 1 chip and the 0.31x row disappears. C is therefore not an anti-chip scheme and does not claim to be; it composes with B (the shadow is in the kernel, the state is in the build).
|
||||
|
||||
**Bit-exactness.** The leaf is the output of the chain's own hash over bytes every node agrees on by consensus; the derivation is integer; nothing vendor-specific is added. The one new consensus-critical function is the canonical serialisation of state (key order, chunking of code), which every node must compute identically: a bug there splits the chain on a day boundary, the class of M20 and the DAA 198,000 incident. The Devnet 2 gate exists for exactly this.
|
||||
|
||||
**Known attacks.** State grinding: a producer can write state (pay gas) to influence leaves; the leaf is hashed with `R_d`, which depends on every leaf, and enters a keyed chained derivation whose read addresses are mixer state, so no bias on 128 dependent reads is reachable at a cost below a block (the MTP lesson, `asic-resistance-history.md` 2.3, is the reason for the hash and the key, not the plain bytes). Compressible state: an attacker fills state with zeros hoping a chip stores it compressed; the hashed leaves are full-entropy, and the padding region (`leaf = 0`) is today's dataset, which the chip already stores at 64 B per item. Precomputation: the snapshot is fixed when `C_day(d)` is certified and `K_d` is known, 20 minutes before the day (the same lead as the epoch seed), and the build is 13 to 77 ms on the GPUs measured (`counter-asic-3-status.md`) plus the leaf pass; a reorg across `C_day(d)` is a merge-depth-scale event, accepted as for the epoch seed (spec 4.3 item 4, O-4.3). Outsourcing: the pool ships the dataset (above). Light evaluation: the f = 0 row above. Long-range: an attacker building an alternative history must build its alternative state snapshots to mine on it, which it does anyway; no change. Finality pause: `C_day(d)` must be certified; if finality is paused for more than the lead the day's snapshot is not derivable and mining would stop, the coupling spec 4.3 argues against for the epoch seed. Rule, as there: take the selected-chain block at that blue score certified or not, deep enough that a reorg across it is a merge-depth event. Empty state at launch: every leaf is `Blake2b(R_d || t || empty)`, full entropy, the dataset as good as today's: graceful.
|
||||
|
||||
**Game theory.** No payment changes. A miner must run or rent a node (or trust a pool's dataset); the solo-mining floor rises by a full node's state (today's devnet: megabytes; a used chain: gigabytes). A pool user sees a 2 GiB daily download or nothing (the pool serves the dataset). A prover that mines already holds state. A holder gains a daily sample of state availability per block, for free. A rollup customer gains nothing directly.
|
||||
|
||||
**Migration.** Class v5 by P2 (object byte 5, 95 percent over a day, floor height, one-sweep rollout, Devnet 2 first). Every node needs the snapshot builder before the flip (a node without it cannot validate PoW after the flip: the floor height's job). Workers need nothing new: the kernel text is unchanged, the dataset arrives from the node's `prepare` line as today, built on the GPU from the cache plus a leaf array the node hands over (2 GiB per day over the local socket) or built on the node's CPU and uploaded. The one-click miner's "nothing to install but the driver" line holds; "nothing to download but the day key" does not.
|
||||
|
||||
### 3.4 The three schemes in one table
|
||||
|
||||
| Scheme | What it is | Chip edge per joule vs the 5090 (f = 1 chip, chip-model-v3 method) | Verifier ms per unit (model, then measured in section 5) | Vendor bit-exactness | Ships as class v5? |
|
||||
|---|---|---|---|---|---|
|
||||
| A, mining is proving | A1 committed codeword, A2 proving steps: dead on bytes and on sampleability; A0 trace-as-dataset survives and is C with the trace as state | A0 unchanged (5.1x GDDR7); A1, A2 not priced | A0: 2.06 + the leaf-read row; A1: 2.9 MB of openings; A2: 32 to 40 ms | A0 adds the zkVM executor to consensus | NEVER as mining = proving; A0 folds into C |
|
||||
| B, tensor-shaped shadow | class v3 plus `8 R` int8 8x8x16 tile steps per hash in the PTX fragment layout, two outputs per lane | memory + `8 R x 1,024 x e_mac x k_mma`; `k_mma` near 1 with a floor near 0.5 (approximate); rows from the measured `e_mac` in 5.3 | 2.06 + about 0.9 microseconds per step per unit scalar (R = 128: +0.9 ms; R = 512: +3.7 ms); VNNI 4x to 16x less | native on sm_75+ and RDNA 3+ (AMD layout unverified); emulated on Pascal, RDNA 2, Apple | before measurement: prototype further. After section 5: NEVER as class content (the block costs the honest card 0.056 to 0.70 pJ per multiply-add, so it forces no joules on a chip); kept as reserve R8 evidence |
|
||||
| C, stored state | the daily dataset derives from the execution state snapshot; kernel unchanged; a state sample per block | unchanged (5.1x GDDR7, 7.5x to 9.2x HBM3); the f = 0 chip disappears | 2.06 + 4,096 leaf reads (section 5.2) | nothing vendor-specific; the serialisation is the consensus risk | before measurement: prototype further. After section 5: SHIP as the class v5 candidate |
|
||||
|
||||
### 3.5 Migration through the class system (common to B and C)
|
||||
|
||||
The P2 rule as designed (`docs/plans/counter-asic-3-node.md` section 6): the header version's high byte carries the producer's object version; epoch `e` is the new class when the window of one day ending at its seed block has at least 9,500 bps of blue blocks at or above the byte, or when the floor height `N` is reached, or when epoch `e - 1` already was; the rule answers v5 only where it would answer v4. For B and C the object byte is 5 and the floor is set at the publish as DAA + 14,400 rounded up to the epoch boundary. The order: Devnet 2 crossing with `tools/fleet/devnet2-gate.sh` (zero rejected blocks across the flip, no reorg over depth 3, exec roots agreeing, a segment record paid, every node on the new version), then the live devnet in one binary sweep (the digest flips), then the flip by signal. A node that synced from a pruning proof takes the floor rule for epochs whose window reaches below its pruning point (the same class as the era witness, status item). For C the floor also bounds how long a non-upgraded node can keep validating PoW: none after the flip, so the sweep must be complete before the floor, which is the 10,800-DAA check already in the rule.
|
||||
|
||||
|
||||
## 4. Reviews: two personas, two short reviews per scheme
|
||||
|
||||
Written by this lane in the persona files' voices (`.claude/agents/cryptographer.md`, `.claude/agents/consensus-engineer.md`): every design claim with its attack, every claim about another chain with its file, and the smallest change the code allows. Each review names the break if there is one.
|
||||
|
||||
### 4.1 Scheme A, mining is proving
|
||||
|
||||
**Cryptographer.** The break is structural and has a name: a lottery needs a distribution of instances and proving has one instance per segment. A1 moves the work into the commit phase and pays for it in witness bytes (2.9 MB per block, or 90 KB with the unit cut to one lane, which also removes `shfl` and with it the only thing that makes the 32-lane unit a unit; `docs/spec/01-lottery-hash.md` 1.9). A2 either recomputes (useless by definition) or verifies a proof (32 to 40 ms measured, `proving-methods.md` 2.1; the gate is 10 ms). The useful fraction bound (proving work per segment over network hashes per segment) is the argument Ball, Rosen, Sabin and Vasudevan make in the negative direction: without a random self-reduction the embedded instance is a constant, and a constant is amortised to zero by the first miner who computes it. A0 is sound as far as it goes and is scheme C. Second attack on A0 that C does not have: the SP1 executor enters the consensus path for the lottery, so an executor bug that produces a different trace on one platform (an undefined-behaviour corner in a precompile patch, a `sha3` or `k256` version skew) splits the chain at the PoW, not at the proof; ledger P7 priced that class for proofs where the native veto bounds the damage to one payout; here nothing bounds it. Verdict: never for A1 and A2; A0 only as C with the trace, and then the state is the better choice of data because every node already agrees on it without a second executor.
|
||||
|
||||
**Consensus engineer.** The code says the same thing from the other side. Block validation in the fork validates the header's PoW before the body is fetched and before execution (`check_pow` on the header path, rusty-kaspa's `header_processor`; the fork's `igneum/exec` runs after the block is accepted into the DAG). A puzzle whose verification needs the segment's trace makes header validation wait on the executor of a segment 20 minutes old, which is fine for a synced node and fatal for IBD: a syncing node must execute every segment of history, in order, to validate the headers of history, so headers-first sync and the pruning proof (which validates headers without bodies) are gone; Kaspa's pruning-point sync (`consensus/src/pipeline/pruning_processor`, approximate location) assumes PoW is a function of the header and a small amount of context. A0 shares this break with C and C answers it in 4.3 by making the snapshot a function of a certified checkpoint's state root plus the state itself, which a pruned node fetches as a snapshot (the exec snapshot path the node already has, CLAUDE.md 6 Oct rule "the p2p snapshot path refuses a snapshot below the node's tip"). For A1 the 90 KB per header kills the header relay and the 600-block record window arithmetic alike. Verdict: never for A1 and A2; A0 is C with a worse data source.
|
||||
|
||||
### 4.2 Scheme B, the tensor-shaped shadow
|
||||
|
||||
**Cryptographer.** No break in the puzzle's soundness: the block is a straight-line integer map with no memory and no data-dependent control flow, its inputs are the per-nonce registers, and the two-output form makes the whole tile load-bearing. Two things to name. (i) The claim that `k_mma` cannot fall far under 1 rests on the honest card's tensor path being near the floor of int8 multiply-add energy; that is an engineering judgement, approximate, and the external chip review (status item 3, `funding.md`) is where it is tested, with the ALU shadow's `k` beside it. The design's advantage over the ALU shadow is bounded, not proven: it narrows the chip's best case from about 0.3 to about 0.5 (approximate) and does not remove the edge. (ii) The fragment layout is a specification of lane ownership; every emulating vendor must reproduce it exactly, and the AMD WMMA layout is unverified (status item 6). Until a 9070 XT run with the spec reference passes, B is bit-exact on one vendor's hardware and in every emulation, which is the state class v2 was in on 3 October (ledger M8) and not a state to cut from. No grinding, outsourcing or precomputation surface is added. A statistical point: the mm8 step's outputs are sums of 16 byte products, so each added value is at most 1,040,400 and its top 12 bits are zero; added into a register it changes the low 20 bits in a structured way. That is fine inside a chain of multiplies and rotates, and the stats run of `TESTS.md` section 3 should be run on the class before any vector is frozen. Verdict: prototype further; a class v5 candidate after the AMD gate and the stats run.
|
||||
|
||||
**Consensus engineer.** No consensus change beyond the class byte and the emitter: the block is kernel text (`igneum-pow/src/emit.rs` gains the step in the three dialects), the verifier (`verify.rs`) gains the step in the register-major loop, the acceptance rule is untouched by construction, and the node's seam is the v5 switch beside the v4 one (`counter-asic-3-node.md` section 1 lists every file the v4 switch touched; v5 is the same list with one more number). The break to name is operational: the fleet splits by hardware generation. Pascal, Volta, RDNA 2 and every Apple card emulate, and the one-click workers compile three paths where they compile one today; the OpenCL path on AMD needs a feature test at compile time (the WMMA builtin exists on gfx11 and gfx12 and not on gfx10, `int8-matrix-family.md` 1), which the pack text must carry as a preprocessor branch, and a wrong branch is a wrong hash, which the self-test catches before the worker serves (`packfile.h`, M28's fix). The 5 percent rule must be checked per emulating card with the block live, and the Apple row is the one most likely to fail it at high `R`. Verdict: prototype further; set `R` from the measured rows with the Apple emulation measured on the M5 Max before any cut; the AMD layout is a hard gate.
|
||||
|
||||
### 4.3 Scheme C, proof of stored state
|
||||
|
||||
**Cryptographer.** The construction is never weaker than today's: the leaf is one more input to the same chained derivation, keyed by the day key through the first mixer, and the f = 1 chip's row is unchanged, which the design says. The break to name is the one Permacoin and MTP both met: who controls the data controls the addresses, unless the data enters through a key the controller does not have. Here the controller of state content (anyone paying gas) does not control `K_d` (a VDF output fixed 20 minutes before the day, spec 4) and the leaf is `Blake2b(R_d || t || key || value)`, so the only lever is choosing content before `R_d` is known, which affects every leaf through `R_d` and none in a predictable way. I find no bias at a cost below a block. The second point: the "verifiable sample of state" is real but modest. A block commits to 128 leaves per lane through 128 dependent reads; a light client checking them needs openings against `R_d` from a full node (depth about 25 at 2^25 leaves, 128 x 25 x 32 B = 102 KB per block if fetched, arithmetic), which is a spot check of availability, not a proof of state correctness; the consensus proof of spec 10 and ledger P4 is still what a light client needs for correctness. The design says this. Third: the canonical serialisation is new consensus-critical code, and the day-boundary flip is the moment it bites (every node rebuilds at once). Verdict: prototype further; a class v5 candidate; the serialisation needs the same test discipline as the DAA switch (a Devnet 2 crossing over a day boundary with a non-trivial state).
|
||||
|
||||
**Consensus engineer.** The break I would have named, the IBD and pruning-proof problem of A0, C answers: the snapshot is a function of `R_d`, a state root at a certified checkpoint, and the node already carries an exec snapshot path (`exec_restart_*` fields, the p2p snapshot message, CLAUDE.md 6 Oct). A syncing node validates historical PoW only by holding every day's snapshot, which is 2 GiB per day of history, which is NOT acceptable for IBD. Fix, the smallest I can see: PoW of blocks below the pruning point is not re-validated (Kaspa's pruning proof validates the proof's headers' PoW; rusty-kaspa `consensus/src/processes/pruning_proof`, approximate), so historical snapshots are needed only for the headers inside the proof, which are a bounded set per level; and for them the proof carries the day's `R_d` and the node either holds that day's state (recent days) or trusts the certificate (the finality rule's lock already makes those headers irreversible). That is a spec item for `docs/spec/10-light-client.md` and the pruning section of spec 02, and it is the same shape as the era-seed witness (status item). Second: the verifier's RAM (+2 GiB, +4 GiB at year 4) and the daily build on a node without a GPU (a seed node, a Hetzner box: the leaf pass is CPU work, measured in 5.2) must stay inside the node's budget; the devnet hands on igneum-build-1 have 128 GB, a home node has 16. Third: a day boundary is now a consensus event that depends on a certified checkpoint 20 minutes before it; under a finality pause (6 October, 18:42Z) the day's snapshot falls back to the uncertified selected-chain block, as spec 4.3 argues for the epoch seed; the rule must be written once and tested on the fast-time harness across a pause. Verdict: prototype further; a class v5 candidate; three spec items (pruning-proof witness, the pause rule, the node RAM budget) before a cut.
|
||||
|
||||
### 4.4 The pick
|
||||
|
||||
B and C are the two to prototype: both are class objects on the shipped hash, both leave the dataset's memory bound untouched, and they compose (B is kernel text, C is the daily build). A is not prototyped: A1 and A2 fail on bytes and on sampleability before any kernel, and A0 is C with a worse data source. The order of merit at this point, before measurement: C first (no vendor risk, unchanged hash rate by construction, a real new property per block, the pool caveat stated), B second (a real lever against the f = 1 chip with a bounded `k`, a vendor split and an unverified AMD layout). Section 6 revisits the order on the measured rows: C holds, B retires into the reserve.
|
||||
|
||||
## 5. The prototypes and the measured rows
|
||||
|
||||
Both prototypes live under `proto-newpow/` with a README carrying the exact commands, the card, the driver and the RESULTS table; this section carries the rows and their consequences. Boxes: two RunPod RTX 4090 24 GB (driver 595.91, nvcc 12.8, `-arch=sm_89`), quiet, the card to itself; CPU rows on igneum-build-1 (EPYC 9454P, one pinned core, `nice -n 19`). Power by `nvidia-smi` at 1 Hz, the mean after the first 10 s of each timed run. Bit-exactness: the PTX path against the reference path on 2^24 lanes (fingerprint), and the GPU against the C CPU reference on 1,024 random lanes.
|
||||
|
||||
### 5.1 `mma-shadow` (scheme B), box 1
|
||||
|
||||
Prototype: `proto-newpow/mma-shadow/` (README with every command, `kernel_mm8.cu` with the PTX path and the `IGNEUM_MM8_REF` reference path, `bench.cu`, `verify_ref.c`, `gen_block.py`, `gen_ref_program.py`, `run.sh`, `out/` with every log and csv). Box 1: RTX 4090 24 GB (128 SMs), driver 570.172 (the box reports 570, not the 595 of box 2), nvcc 12.8.93, `-arch=sm_89`, 1 warp per block, 10 timed batches of 2^24 after a warm-up, power from `nvidia-smi` at 1 Hz over a 25-s sustained phase (mean after its first 10 s), idle 15.0 to 15.3 W. The two-output tile form of section 3.2 was built (the single-output form never was). Run 19:36 to 19:52 UTC.
|
||||
|
||||
| R (steps per iteration) | mm8 per hash (8 R) | MH/s (GPU time) | Ratio to R = 0 | Watts, mean | Block watts over R = 0 | SM MHz | Microjoules per hash | Marginal pJ per multiply-add | Fingerprint (2^24 at base 0) | PTX = reference path | CPU = GPU (1,024 lanes) | Verifier block delta, ms per unit (box core, scalar C) | Registers per thread |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 0 (control) | 0 | 63.08 | 1.000 | 201.2 | 0 | 2,670 | 3.19 | | 7c28cfb06c5c65a9 (the pack's; the 3 pack vectors PASS standalone and in batch) | yes | 1,024 of 1,024 | 0 (the R = 0 run read -0.10, noise) | 29 |
|
||||
| 8 | 64 | 63.08 | 1.000 | 204.1 | 2.9 | 2,670 | 3.24 | 0.70 | 06fc2593bfb94b4f | yes | 1,024 of 1,024 | +0.05 | 30 |
|
||||
| 32 | 256 | 63.08 | 1.000 | 207.7 | 6.5 | 2,670 | 3.29 | 0.39 | 26e83a65f519c865 | yes | 1,024 of 1,024 | +0.17 | 29 |
|
||||
| 128 | 1,024 | 63.08 | 1.000 | 212.7 | 11.5 | 2,670 | 3.37 | 0.17 | 42223c2113188335 | yes | 1,024 of 1,024 | +1.14 | 32 |
|
||||
| 512 | 4,096 | 63.08 | 1.000 | 215.9 | 14.7 | 2,670 | 3.42 | 0.056 | 02b7002d747f3711 | yes | 1,024 of 1,024 | +4.39 | 36 |
|
||||
|
||||
Sustained rates over the power window 63.07 to 63.03 MH/s at every R; wall and event rates agree to 0.01 MH/s; no spills, no stack, temperature 58 to 64 C, no throttling. The verifier totals in the box's logs (about 10 ms per unit) are a naive scalar interpreter with a lazy `mh_word` per load on a shared Zen 4 core at load 13 and are not comparable to the project's Rust verifier (2.06 ms per unit on an M5 Max core, interleaved); the block delta is the number: about 1.1 microseconds per `mm8` step per unit, 32 lanes x 32 byte products in plain loops. (At R = 512 the card issues about 258 G tiles per second, 2.6 x 10^14 multiply-adds per second, in the region of 40 percent of the 4090's dense int8 tensor peak, approximate from the published TOPS figure, so R = 512 is near the top of the free band, not its middle.)
|
||||
|
||||
What the rows say:
|
||||
|
||||
1. The tensor block is free in hash rate to R = 512 on the 4090: 4,096 tile instructions per hash leave the rate at 63.08 MH/s to the third decimal. The kernel is latency-bound on its 128 dependent loads and the tensor work fills stalls that were already there, as the ALU shadow did on the 5090 to 150,800 ops (`latency-shadow-2026-10-06.md` 5).
|
||||
2. The block costs the honest card almost nothing in energy: 2.9 to 14.7 W, 0.70 pJ per multiply-add at R = 8 falling to 0.056 pJ at R = 512 (the tensor path's fixed cost amortised), 0.05 to 0.23 microjoules per hash on a 3.19 microjoule hash (+1.6 to +7.2 percent). The ALU shadow at N = 100,000 costs the 5090 0.6 microjoules per hash (11 pJ per counted op, `latency-shadow-2026-10-06.md` 5, item 4); the tensor block at its free-band ceiling costs a third of that.
|
||||
3. That is the finding, and it is negative for the scheme's purpose (section 5.3): a shadow lever moves the chip's edge only by the joules it makes the HONEST card spend on work the chip cannot do more cheaply. The tensor path is so efficient on the GPU that the block adds 0.23 microjoules at most, so at `k = 1` the chip's edge falls from 6.9x to 4.9x on GDDR7 against this 4090, where the ALU shadow took the 5090 from 5.6x to 2.1x, and the tensor block costs the verifier 26x more per unit of chip-forcing energy (4.39 ms scalar per 0.23 microjoules against 0.17 ms per 0.6 microjoules). The property the design hoped for (`k_mma` near 1 because the GPU's tensor engine is near the floor) is real and is exactly why the lever is weak: there are no joules in it to force.
|
||||
4. The correctness chain holds at every rung: the PTX fragment read and the plain-integer reference agree on all 2^24 lanes at every R, and the CPU interpreter matches the GPU on 1,024 lanes at every R; the probe's fragment layout (`family-probe.cu` mm8 `warp_ref`) was used as written and needed no correction. Registers 29 to 36, occupancy unchanged. This is the first class-shaped evidence that an `mm8` family is cheap and exact for the honest NVIDIA card, which is what the reserve entry R8 needs; it is not evidence for a class v5.
|
||||
|
||||
Consequences per tier (B at R = 128, the largest R whose scalar verifier delta stays near 1 ms):
|
||||
|
||||
| Tier | Meaning | What to do |
|
||||
|---|---|---|
|
||||
| NVIDIA Turing and later (RTX 20 to 50 series), any memory size | rate unchanged, +11.5 W on a 4090 (+5.7 percent), per-pound unchanged, per-watt 0.95x | nothing; the block would not be adopted on this evidence |
|
||||
| NVIDIA Pascal (GTX 10 series) | emulation at about 16 steps per `mm8` (approximate): 1,024 per hash is about 16,000 counted steps, inside the latency shadow of a 1080-class card by the counted-ops rule, approximate; unmeasured | the emulation path exists in the kernel text (`IGNEUM_MM8_REF`) and is bit-exact; a GTX 1080 row is owed if B ever proceeds |
|
||||
| AMD RDNA 3 and 4 | the WMMA layout is unverified (status item 6); emulation otherwise at about 12 to 16 steps per `mm8` | the gate of section 7 rank 2 stays open; not worth running unless B proceeds |
|
||||
| Apple | emulation at about 10 steps per `mm8` (approximate), 10,000 counted steps at R = 128 inside the M5 Max's 130,000 ceiling | owed; not worth running unless B proceeds |
|
||||
| Rig, pool user | nothing changes in shares; a rig pays the block's watts per card | nothing |
|
||||
| Node operator (verifier) | +1.14 ms per unit scalar at R = 128 (+55 percent of 2.06 ms), +4.39 ms at R = 512; a SIMD byte-dot path would cut it 4x to 16x (approximate) | the verifier cost per joule forced is the reason the scheme loses to the ALU shadow |
|
||||
| A chip | must carry a tensor array per 32 lanes in flight; at the measured block energy it pays at most 0.23 microjoules per hash more than today at `k = 1`, 0.07 at `k = 0.3` | the chip's edge barely moves (5.3) |
|
||||
|
||||
### 5.2 `state-dataset` (scheme C), box 2 and igneum-build-1
|
||||
|
||||
Prototype: `proto-newpow/state-dataset/` (README with every command and log; `kernel_sd.cu` = the pack's kernel plus `igneum_leaves` and `igneum_build_sd`, the hash kernel byte for byte the pack's; `verify_sd.c` the 32-lane C interpreter and the item check; `cpu_rows.c` the igneum-build-1 rows). Box 2: RTX 4090 24 GB, driver 595.91, nvcc 12.8, host EPYC 7352; igneum-build-1 EPYC 9454P at load 19 to 32 (shared), one core pinned, nice 19. Run 19:34 to 19:50 UTC. The leaf stand-in: one ChaCha12 block of (sigma, S, t, 0, tag) with `S = K XOR 0x5a5a5a5a` (the README defines it).
|
||||
|
||||
| Row | Control (mx8-genesis) | sd1 | Reading |
|
||||
|---|---|---|---|
|
||||
| Hash rate, 10 batches of 2^24, two passes | 63.083, 63.083 MH/s | 63.088, 63.087 MH/s | equal within 0.01 percent: the kernel is the same binary over a dataset of the same shape |
|
||||
| Watts, mean after 10 s of a 20-s window; SM MHz | 207.6, 205.2 W; 2,745 | 207.9, 206.5 W; 2,745 | equal within the 2 W run-to-run noise; 3.27 microjoules per hash on this 4090 (the 5090 is 2.34 to 2.65) |
|
||||
| MH/s per W | 0.304, 0.307 | 0.303, 0.305 | unchanged |
|
||||
| Cache fill, GPU | 1.81, 1.85 ms | 1.85, 1.85 ms | unchanged |
|
||||
| Leaf array (2^24 ChaCha12 blocks, 1 GiB) on the GPU | none | 5.49 ms | the synthetic stand-in; a real snapshot comes from the node |
|
||||
| Dataset build, second pass | 30.55 ms | 31.98 ms | +1.43 ms (+4.7 percent): one coalesced 64 B read per item |
|
||||
| Chunked build (leaves streamed from pinned host memory in 64 MiB or 256 MiB chunks, one stream) | | 75.8 ms, 75.5 ms | PCIe-bound: 62 ms of copy (17.2 GB/s on this box) plus the build, partly serialised; two streams would hide most of the build (not done) |
|
||||
| Device memory during the build (context 395 MiB included) | 1,675 MiB | 2,699 MiB resident leaves; 1,741 MiB chunked at 64 MiB; 1,933 at 256 MiB | while hashing 1,803 MiB in every mode (leaves freed) |
|
||||
| 2^24 fingerprint at base 0 | 7c28cfb06c5c65a9 (the pack's, both passes) | d5b0c16390cad0e8 (four runs) | |
|
||||
| Pack vectors (3 warps, standalone and in batch); dataset head, word [MASK], 64 Mac samples | PASS | n/a (new values; sd1 base 0 lane 0 = b600edbed969becc) | the harness is the pack's |
|
||||
| Build bit-exact: 1,024 random items, GPU words against the plain C host derivation | 1,024 of 1,024 | 1,024 of 1,024 (also after both chunked rebuilds); 64 device leaves = host leaves | |
|
||||
| Hash bit-exact: 4 dumped warps (128 lanes) against the C interpreter with lazy `mh_word` / `mh_word_sd` | 128 of 128 (and 32 of 32 against the Mac vector at base 0) | 128 of 128 | |
|
||||
| Host cache FNV-1a 64 | 48c4f5bf24166b2e = the Mac's | same | |
|
||||
|
||||
The CPU rows (igneum-build-1, ms per unit of 4,096 items, 100 units; two readings at load 19 and 31):
|
||||
|
||||
| Row | Reading 1 | Reading 2 | Per item |
|
||||
|---|---|---|---|
|
||||
| (i) 4,096 random 64 B reads from a 2 GiB resident leaf array | 0.108 ms (0.092 to 0.172) | 0.163 ms | 26 to 40 ns |
|
||||
| (i) the same from an 8 GiB array (the year-12 size) | 0.139 ms | 0.209 ms | 34 to 51 ns |
|
||||
| (ii) 4,096 leaves derived on the fly (one ChaCha12 block each) | 0.284 ms | 0.285 ms | 69 ns |
|
||||
| (iii) 4,096 x `mh_item` on the host cache, naive, no interleaving | 9.01 ms | 11.24 ms | 2.2 to 2.7 microseconds |
|
||||
| 4,096 x (leaf read + `mh_item_sd`), naive | 9.31 ms | 11.64 ms | |
|
||||
| Daily leaf array on CPU (2^24 blocks): one core / 32 threads | 1.67 s / 0.073 s | 1.68 s / 0.083 s | 100 ns per leaf |
|
||||
| Host cache fill (256 MiB), one core | 0.447 s | 0.452 s | |
|
||||
| Light-client sample: distinct items touched by lane 0's 128 loads | 128 of 2^24 | | 128 openings x 25 levels x 32 B + 128 x 64 B = 108 KiB per lane; 3.4 MiB per 32-lane unit before dedup (arithmetic) |
|
||||
|
||||
Reading, against the gate: the project's Rust verifier does the 4,096 derivations in 2.06 ms on an M5 Max core by interleaving the eight dependent cache misses across items (the naive C figure here, 9 to 11 ms, is an upper bound and is not the production verifier); sd1 adds 0.11 to 0.21 ms per unit when the verifier holds the state in RAM (+5 to +10 percent of 2.06 ms; about +0.3 to +0.5 ms on a 2019-class core by the 2.5x rule), so the class v3 verifier plus sd1 sits at about 2.2 ms on the M5 Max core and about 5.7 ms on the 2019-class core, inside the 10 ms gate with the x8 margin intact. The snapshot's own cost is the state read on the node, not the leaf hashing (1.7 s on one core for 2^24 leaves).
|
||||
|
||||
Consequences per tier (sd1):
|
||||
|
||||
| Tier | Meaning | What to do |
|
||||
|---|---|---|
|
||||
| Home miner, one 8 GB card | hash rate, watts and MH/s per W unchanged (measured equal); the build must stream the leaves (resident leaves at the designed 2 GiB dataset would peak at about 4.6 GiB, approximate; chunked 64 MiB about 2.7 GiB, approximate, measured 1,741 MiB at 1 GiB), so the 8 GB mine-and-prove profiles of `prover-tiers-real-cards.md` keep their memory | ship chunked only; the kernel already takes an item range, so chunking needed no kernel change |
|
||||
| 12, 16, 24, 32 GB cards | unchanged in rate and watts; the daily build +1.4 ms resident or about 76 ms streamed (about 150 ms at 2 GiB, approximate) | nothing |
|
||||
| AMD and Apple | the hash kernel is untouched, so the measured class v3 rates stand (18.9 MH/s on the 9070 XT, 27.1 on the M5 Max); the build kernels gain one 64 B read per item in OpenCL and Metal | the two build kernels in the emitter's other dialects |
|
||||
| Rig | per card as above; one 2 GiB snapshot per day per rig, shared by its cards | the node beside the rig serves it over loopback |
|
||||
| Pool user | a 2 GiB download per day from the pool, or the pool ships the built dataset; today a pool user needs nothing but the day key | the pool protocol (spec 09) gains a daily snapshot or dataset fetch |
|
||||
| Solo miner | must run a node with execution (it already does for templates) and hold the state | nothing new beyond RAM |
|
||||
| Node operator | +2 GiB RAM for the snapshot (+4 GiB at year 4), a daily serialisation pass, the verifier +0.11 to 0.21 ms per unit | the node RAM budget stays inside a 16 GB home node at launch sizes |
|
||||
| Light client, holder | a verifiable sample of 128 state leaves per lane per block exists; checking it costs 108 KiB per lane fetched on demand, so it is an availability spot check, not a light verification of PoW | rank 6 of section 7 |
|
||||
| Prover, rollup customer | nothing | |
|
||||
|
||||
### 5.3 The chip rows on the measured numbers
|
||||
|
||||
Method: `sim/horizon/new-pow/chip_rows.py` (the chip-model-v3 section 5.4 f = 1 chip plus the block's measured energy times `k`; the card figures are 5.1's; every chip figure is arithmetic on `chip-model-v3.md`'s cited and approximate memory figures). The denominator here is the 4090 measured tonight (3.19 microjoules per hash at the control, a card whose memory system is half the 5090's), not the 5090, so the R = 0 column reads 6.9x where `chip-model-v3.md` reads 5.1x for the 5090; the movement with R is the row that matters.
|
||||
|
||||
| Scheme and setting | Honest card microjoules per hash | Chip, GDDR7, k = 1 / 0.5 / 0.3 (microjoules) | Edge over the card, GDDR7 | Chip, one HBM3 stack, k = 1 / 0.5 / 0.3 | Edge, HBM3 |
|
||||
|---|---|---|---|---|---|
|
||||
| Class v3 today (R = 0, the 4090 control) | 3.19 | 0.466 | 6.9x | 0.321 | 9.9x |
|
||||
| B at R = 128 (1,024 tiles per hash, block 0.18 microjoules) | 3.37 | 0.646 / 0.556 / 0.520 | 5.2x / 6.1x / 6.5x | 0.501 / 0.411 / 0.375 | 6.7x / 8.2x / 9.0x |
|
||||
| B at R = 512 (4,096 tiles per hash, block 0.23 microjoules, the free band's top) | 3.42 | 0.696 / 0.581 / 0.535 | 4.9x / 5.9x / 6.4x | 0.551 / 0.436 / 0.390 | 6.2x / 7.8x / 8.8x |
|
||||
| For comparison, the ALU shadow at N = 100,000 on the 5090 (`latency-shadow-2026-10-06.md` 6, measured 3.27 microjoules at the 431 W cap) | 3.27 | 1.59 / 1.03 / 0.80 | 2.1x / 3.2x / 4.1x | 1.44 / 0.88 / 0.66 | 2.3x / 3.7x / 5.0x |
|
||||
| C (sd1) at any size | unchanged (measured equal) | 0.466 | unchanged | 0.321 | unchanged; the f = 0 recompute chip must add 2 GiB of DRAM and becomes this chip |
|
||||
|
||||
Reading: B moves the f = 1 chip's edge by 1.4x to 2x at `k = 1` and by 1.1x at `k = 0.3`, at a verifier cost of 1.1 to 4.4 ms per unit (scalar); the ALU shadow moves it by 2.7x at `k = 1` and 1.4x at `k = 0.3` at 0.17 ms per unit. On every axis the measured tensor block is the weaker lever, for the reason stated in 5.1 item 3. C moves nothing and claims nothing against the chip; its merit is elsewhere.
|
||||
|
||||
## 6. Verdicts
|
||||
|
||||
| Scheme | Verdict | Why, in one line |
|
||||
|---|---|---|
|
||||
| A, mining is proving | NEVER (A1, A2); A0 folds into C | one proof per segment is not a distribution of puzzles; the bytes (2.9 MB of openings per block) or the verify (32 to 40 ms) kill every form that is not "hold the trace", and holding the trace is C with a worse data source |
|
||||
| B, tensor-shaped shadow | NEVER as class v5 content for the anti-chip purpose; the measurement (0.056 to 0.70 pJ per multiply-add, 15 W for 4,096 tiles per hash) is the reason. KEEP the `mm8` family as reserve R8 with the two-output correction, for datapath diversity, not for joules |
|
||||
| C, stored state | SHIP AS CLASS v5 CANDIDATE (through the spec items of 4.3 and the Devnet 2 gate): hash rate and watts unchanged by construction and measured equal, build +1.4 ms, verifier +0.11 to 0.21 ms per unit, bit-exact on 1,024 items and 128 lanes; a new property per block (a random sample of state) and a new requirement per mining operation (hold the state); the open decision is what a header verifier is asked to hold |
|
||||
|
||||
**A, in full.** The mandate asked for something never done, and "mining is proving" is the thing everybody has wanted and nobody has shipped; this lane's contribution is the reason, stated as a bound rather than a feeling: the useful fraction of a proving-as-lottery scheme is (proving work per segment) / (network hashes per segment), 8 percent at 1 GH/s and 0.08 percent at 100 GH/s on this chain's measured figures, because gas sets one and the security budget sets the other, and a puzzle whose verifier either recomputes the piece or verifies a 32 to 40 ms proof cannot sit under a 10 ms gate. The 80/20 split stays. Ledger F13's answer stands and gains this bound. What survives (A0) is scheme C.
|
||||
|
||||
**B, in full.** The scheme asked whether a GPU-shaped puzzle could make a chip into a GPU. The answer the 4090 gives is that the tensor-shaped work is free for the honest card (rate unchanged to R = 512, 15 W at most for 4,096 tiles per hash) and therefore nearly free for the chip too: a lever against the f = 1 memory-controller chip works only through joules the honest card is forced to spend on work the chip cannot do more cheaply, and the ALU shadow of class v4 spends 11 pJ per op where the tensor path spends 0.056 to 0.70 pJ per multiply-add. The chip's edge moves from 6.9x to 4.9x at k = 1 (4090 denominator) where the ALU shadow moved the 5090's by 2.7x (5.6x to 2.1x), and the verifier pays 26x more per unit of forcing energy (4.39 ms per 0.23 microjoules against 0.17 ms per 0.6). There is also the vendor split (native on Turing and later and on RDNA 3 and later, emulated on Pascal, RDNA 2 and every Apple card) and the unverified AMD layout. So: never as class v5 content for this purpose. What the measurement does establish, and what should be kept: an mm8 family is exact on NVIDIA at every R tried (2^24 lanes PTX = reference, 1,024 lanes CPU = GPU at five rungs), cheap for the honest card, and bounded in verifier cost by a known law (1.1 microseconds per step per unit, scalar); that is the evidence the reserve entry R8 lacked, and the two-output form (both tile outputs consumed) is a correction R8 needs so a chip cannot halve the tile. The standing rule this leaves for the programme: a shadow lever only works through joules the honest card is forced to spend, so shadow work goes where the GPU is LEAST efficient per op among the operations a chip cannot do much better (RandomX's argument), never where it is most efficient.
|
||||
|
||||
**C, in full.** The scheme does what it says at a cost that is measured and small: the hash kernel is byte for byte the shipped one, the 4090's rate and watts are equal within noise (63.083 against 63.088 MH/s, 205 to 208 W), the daily build grows by 1.4 ms resident or about 76 ms streamed over PCIe (about 150 ms at the designed 2 GiB, approximate), device memory during the build stays at dataset plus cache plus one 64 MiB chunk, the verifier grows by 0.11 to 0.21 ms per unit with the state in RAM (about 2.2 ms on the M5 Max core, about 5.7 ms on a 2019-class core by the 2.5x rule, inside the 10 ms gate), and every GPU word and lane agrees with the plain C derivation (1,024 of 1,024 items, 128 of 128 lanes). It is never weaker than today's dataset against any chip and it removes the f = 0 recompute chip as a category. What it adds has not been done on a shipped chain in this form: the dataset IS the keyed execution state, so every mining operation holds the chain, and every block names 128 random state leaves per lane that any full node can open against the day's state root. Its limits are stated: a pool can ship the dataset (a 2 GiB daily delivery per pool miner, where today a pool miner needs only the day key), a header verifier must hold the state or be shown 3.4 MiB of openings per unit (so light clients still rely on certificates, as spec 10 says), and the canonical serialisation is new consensus-critical code that must cross a day boundary and a finality pause on the fast-time harness before Devnet 2. Verdict: ship as the class v5 candidate, with the three spec items of 4.3 (the serialisation, the pruning-proof witness, the pause rule), the Devnet 2 gate, and the open decision on what a header verifier is asked to hold in front of it.
|
||||
|
||||
**The ranking after measurement.** C, then nothing else from this lane as class content; B retires into the reserve entry it improves; A is recorded as a bound. The brief's hope of "mining is proving" is answered with the reason it cannot be, which is worth more to the project than a design that pretended otherwise.
|
||||
|
||||
## 7. Ranked next steps
|
||||
|
||||
Hours are agent hours (the project lead's rule: Claude-side work takes hours). Each gate is a measurable pass line. Consequence per tier is the row's own. Ranks 2, 3 and 4 were written as B's gates before the ladder landed; after section 5 they are WITHDRAWN (B is not carried forward as class content; the rows stay so the reasoning is visible) and the live order is 1, 5, 6, 7, 8.
|
||||
|
||||
| Rank | Proposal | Evidence | Model | Hours | Consequence per tier | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Scheme C as class v5 content: the daily dataset derives from the state snapshot (`sd1`), spec text for 1.8.5 (the leaf line), 1.12 (the snapshot's checkpoint and lead), 10 (the pruning-proof witness), 4.3's pause rule applied to the day boundary | section 5.2: the hash kernel and rate are unchanged by construction, the build and verifier costs are the measured rows; every miner operation must hold state | the verifier row: 2.06 ms + the leaf-read row per unit; node RAM + the dataset size | spec 3 h; emitter and `memhard.rs` leaf line 2 h; node snapshot builder (canonical serialisation, one pass per day, the `prepare` hand-over) 6 h; fast-time harness across a day boundary and a finality pause 3 h; Devnet 2 crossing 2 h | 8 to 32 GB cards: no hash-rate change, +2 GiB device memory during the build only (streamable); rig: the same per card; pool user: a 2 GiB daily download or nothing; solo miner: a full node's state; node operator: +2 GiB RAM (+4 at year 4) and a daily leaf pass; holder: a daily random sample of state per block; prover, rollup customer: nothing | the fast-time 3-node network crosses a day boundary with a non-trivial state and no fork; verifier under 10 ms on a 2019-class core with the snapshot in RAM; Devnet 2 PASS over a day boundary |
|
||||
| 2 | Scheme B's AMD gate: the WMMA iu8 fragment layout on the RX 9070 XT against the spec reference (the two-output tile), a PC 1 job with the card alone | status item 6 (layout unverified); section 5.1's bit-exact rows on NVIDIA | the spec layout as the definition; a lane permutation per vendor | 3 h (the probe exists: `family-probe.cu` mm8 row; the OpenCL twin needs the builtin path and the permutation) | AMD 16 GB tier: decides whether RDNA 3 and 4 run B natively or emulate at about 12 to 16 steps per mm8 | 1,024 random units bit-exact on the 9070 XT, both tile outputs |
|
||||
| 3 | Scheme B's Apple row: the emulated mm8 block in Metal on the M5 Max at R = 32, 128, 512, hash rate and watts under `with-lock.sh measure` | section 3.2's vendor table: Apple is the honest card's worst case; the M5 Max binds at about 130,000 counted ops | 4 shuffles plus 4 dot4 emulations per step, about 10 steps per mm8 (approximate) | 3 h | Apple tier: the 5 percent rule decides the largest R the class can carry | rate within 5 percent of mx8 at the chosen R; bit-exact against the C reference |
|
||||
| 4 | Scheme B as class v5 content beside C (`mx8+sd1+mm8xR`) once ranks 2 and 3 pass: the emitter's three dialects (inline PTX with the `IGNEUM_MM8_REF` fallback for sm_61 to sm_72, the AMD builtin behind a feature test, the Metal emulation), the verifier step in `verify.rs`, the stats and fuzz runs of `TESTS.md` on the class, the v5 switch beside v4 | section 5.1 and 5.3: the block's measured watts and the chip rows at k | the chip rows of 5.3 | emitter 4 h; verifier 1 h; suites 2 h; node seam 2 h; Devnet 2 2 h | NVIDIA Turing and later, AMD RDNA 3 and later: native, the measured watts; Pascal, RDNA 2, Apple: emulation at the measured 5 percent check; pool user: nothing; chip: a tensor array per 32 lanes in flight | the six gates of `counter-asic-2-rollout.md` 7 plus the AMD and Apple rows; verifier under 10 ms on a 2019-class core at the chosen R |
|
||||
| 5 | The reserve text correction: R8 `mm8` consumes both tile outputs (two destinations) so a chip cannot halve the work | section 3.2 (the half-tile shortcut) | arithmetic: 32 of 64 products consumed in the single-output form | 1 h (spec 1.13.2 text and the edge vectors) | none until era 4 or a signal | the R8 edge vectors re-cut for two outputs, bit-exact on the three vendors |
|
||||
| 6 | The light-client state sample: a node RPC that returns, for a block, the 128 leaves of lane n with openings against R_d, and a client check | section 3.3 (the sample is real but modest: availability, not correctness) | 128 x 25 x 32 B = 102 KB per block fetched on demand (arithmetic) | 4 h | holder and light client: a spot check of state availability per block; node: one RPC | 128 openings verify against R_d on the fast-time network |
|
||||
| 7 | The 2019-class core measurement (O-1.14) for the verifier under C and under B at the chosen R | every verifier row here is M5 Max or Zen 4 plus the 2.5x rule | the 2.5x rule stands in | 1 h once a core is found (a 2019 laptop or a rented older CPU box) | every tier: the gate that fixes R and the snapshot read budget | under 10 ms per unit, worst cold |
|
||||
| 8 | Do not build scheme A (mining is proving) in any form; record the sampleability bound (proving work per segment over network hashes per segment: 8 percent at 1 GH/s, 0.08 percent at 100 GH/s) in the ledger beside F13 | section 3.1 and 4.1 | the bound's arithmetic | 0.5 h (a ledger row) | none | none |
|
||||
|
||||
One paragraph each.
|
||||
|
||||
**1. Scheme C first.** It is the cheapest change with a new property: the kernel text, the hash rate and the chip rows are untouched, so every measured number of Counter ASIC 2.0 and 3.0 stands; what changes is the daily build and the node. The cost is a canonical state serialisation in consensus, which is why the fast-time harness must cross a day boundary with state and a finality pause before the Devnet 2 crossing. The pool caveat is stated: the design forces the operation, not the card, to hold state.
|
||||
|
||||
**2 and 3. B's two vendor gates.** B is bit-exact tonight on NVIDIA (section 5.1) and in every emulation; it is not a class candidate until an AMD card reproduces the spec layout and the Apple emulation's hash-rate cost is measured. Both are short jobs with the card alone.
|
||||
|
||||
**4. B beside C.** The order matters: C lands first because it needs no vendor work; B joins the same class byte or the next one once ranks 2 and 3 pass, with R set from 5.1 and the 2019-core row.
|
||||
|
||||
**5 to 8.** Small, named, and each closes a thread this lane opened.
|
||||
|
||||
## 8. Open questions and what could not be run
|
||||
|
||||
| Question | Why it could not be closed tonight | What closes it |
|
||||
|---|---|---|
|
||||
| The 2019-class core (O-1.14) | no such core in the fleet; igneum-build-1 is Zen 4, the Mac is M5 Max; the 2.5x rule stands in | rank 7 |
|
||||
| The AMD WMMA fragment layout | PC 1 is the project lead's desk and the AMD rows were owed all day (status file); no AMD card on RunPod or Vast tonight (fleet agent) | rank 2 |
|
||||
| The Apple emulation of mm8 | a Metal emulation kernel is a 3-hour job and the Mac measure lock was free; not started because the AMD gate decides first whether B proceeds | rank 3 |
|
||||
| The canonical state serialisation for C | a design item that touches the exec layer (`igneum/exec`), out of this lane's files | rank 1 |
|
||||
| The pruning-proof witness for C's historical PoW | spec 02 and 10 items | rank 1 |
|
||||
| Whether the tensor path's marginal energy on the 5090 differs from the 4090's | one card measured (box 1); the 5090 is on the project lead's desk | a PC 2 job with the same `run.sh` |
|
||||
| The Ampere row (3060, 3080, 3090) | every Ampere card of the fleet was mining and proving the live devnet; a loaded 3090 was offered and declined (a loaded card's rate is not a number) | one quiet Ampere pod |
|
||||
| The verifier with a byte-dot instruction (VNNI, NEON udot) | the C reference is scalar | 1 h: an AVX-VNNI and a NEON path in `verify_ref.c`, measured on both cores |
|
||||
| Scheme C's leaf array on an 8 GB card at the 2 GiB design size | the build holds dataset + cache + leaves on the device (4.3 GiB) unless chunked | the chunked build (section 5.2 says whether it is trivial) |
|
||||
|
||||
## 9. Summary for the coordinator
|
||||
|
||||
This lane wrote three candidate proofs of work for Igneum, reviewed each in two personas, prototyped the two that survived on real cards, and measured. (1) Scheme C, the dataset derived from the chain's execution state, is the one to carry forward as the class v5 candidate: measured on an RTX 4090 the hash rate and watts are unchanged to the third decimal (63.08 MH/s, 207 W, the kernel is byte for byte the shipped one), the daily build grows by 1.4 ms resident or about 76 ms streamed, the verifier by 0.11 to 0.21 ms per unit with the state in RAM, every GPU item and lane agrees with the plain C derivation (1,024 of 1,024, 128 of 128); it forces every mining operation to hold state, names 128 random state leaves per lane per block, and removes the f = 0 recompute chip as a category; its costs are a 2 GiB daily delivery for pool miners and three spec items (canonical serialisation, a pruning-proof witness, the pause rule). (2) Scheme B, a tensor-shaped shadow of int8 8x8x16 tiles, is bit-exact on NVIDIA at every rung (2^24 lanes PTX = reference, 1,024 lanes CPU = GPU, R = 8 to 512) and free in hash rate to 4,096 tiles per hash, and that is exactly why it fails as an anti-chip lever: it costs the honest card 0.056 to 0.70 pJ per multiply-add and at most 0.23 microjoules per hash, so the f = 1 chip's edge moves from 6.9x to 4.9x at k = 1 against the 4090 where the class v4 ALU shadow moved the 5090's from 5.6x to 2.1x, at 26x the verifier cost per joule forced; never as class content, kept as the evidence and the two-output correction for reserve family R8. (3) Scheme A, mining is proving, is never: one proof per segment is not a distribution of puzzles; the useful fraction is bounded by proving work per segment over network hashes per segment (8 percent at 1 GH/s, 0.08 percent at 100 GH/s on this chain's measured figures); every form that is not "hold the trace" dies on 2.9 MB of openings per block or a 32 to 40 ms proof verify, and "hold the trace" is scheme C with a worse data source.
|
||||
|
||||
1. Scheme C costs nothing in hash rate or watts (63.083 against 63.088 MH/s, 205 to 208 W on the 4090, measured) and 0.11 to 0.21 ms per unit of verifier time (measured on igneum-build-1), so class v5 can carry it; the model is `item_sd(t) = item(t) with s ^= leaf(t)` over the day's certified state root.
|
||||
2. The tensor shadow moves the f = 1 chip's per-joule edge by at most 1.4x (6.9x to 4.9x at k = 1, R = 512) for 4.39 ms of scalar verifier per unit, against 2.7x for 0.17 ms from the ALU shadow; the model is `chip_rows.py` on the measured block energy of 0.23 microjoules per hash.
|
||||
3. Mining cannot be proving on a lottery with a 10 ms verifier: the useful fraction is bounded by 5 GPU-seconds of proving per 8-second segment over the network's hashes in that segment, 8 percent at 1 GH/s and falling with the hash rate; the model is that ratio on `prover-tiers-real-cards.md` and bench-log 2582.
|
||||
491
docs/analysis/horizon/polish.md
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
# Horizon lane 6: polish. Every shipped system against the best in its class
|
||||
|
||||
Date: 6 October 2026, evening UK (written 20:00 to 21:00Z, while the live devnet's finality was still paused). Lane 6 of the Horizon programme. Worktree `/Users/joshm/Projects/igneum-wt-horizon` (branch `horizon`, HEAD c3aa502). Output: this file only. No file outside it was edited; nothing was built, deployed, posted or started; the installed app and every live service were read, never touched.
|
||||
|
||||
the project lead's bar: "a level of polish that has not been seen before." This file is the honest audit: what each shipped system does, the named comparator and the exact screen or feature it has that we lack or do worse, the ledger rows that close the gap (95 rows, ids Q1 to Q105 with gaps, each with a file or screen, a severity, hours of agent work, an owner and a gate), and what is already better than the comparator. The first ten rows are what the project lead will notice first when he opens each product tomorrow.
|
||||
|
||||
## 0. What was read and run
|
||||
|
||||
Read, in the worktree: `app/igneum-app` (src, ui, tests), `app/mac/IgneumMiner.swift`, `app/windows/host.cpp`, `docs/plans/miner-ui-2.md`, `miner-ui-3.md`, `miner-ui-3-audit.md`, `ember-tune.md`, `release-0.3.14.md`, `docs/design/app-screens/`, `docs/design/miner-tuning.md`, `docs/fud-ledger.md` (X17, X19, X21 to X28, M25 to M29, G7, G10, G13), `site/` (every page, `api/`, `lib/`, `verify/`, `build.mjs` read not run, `scrub.mjs`, `forbidden-strings.txt`, `downloads.json`, `journey.json`, `vercel.json`, `sitemap.xml`), `docs/design/site-polish-2026-10-04.md`, `docs/plans/site-ui-3-shots/`, `docs/plans/site-miner-2026-10-06.png`, `docs/review/round-4-reddit-2026-10-06.md`, `docs/plans/explorer.md` and `explorer/`, `relay/` (ui, api, lib, clients, playbooks, tests), `tools/relay.mjs`, `tools/console.mjs`, `tools/jobs.mjs`, `docs/design/relay/`, `docs/design/console/`, `pool/` (src, web, tests, README), `docs/plans/pool.md`, `docs/spec/09-pool-protocol.md`, `tools/community/discord-hooks.mjs` and its tests and fixtures, `docs/community/discord-hooks.md`, `tools/fleet/`, `tools/build-job.mjs`, `tools/build-remote.sh`, `tools/ci/`, `docs/plans/build-job.md`, `build-server.md`, `hands-on-build-1.md`, `node-changes.md`, `packaging/README-ship.md`, `packaging/ota/README.md`, `packaging/mac/README.md`, `packaging/windows/README.md`, `tools/observer/` (observer.mjs, README), `docs/spec/03` section 3.9, and this lane's siblings `finality-and-weight.md` (3.1, 6) and `frontier.md` (3.7).
|
||||
|
||||
Read, read-only, in other agents' worktrees: `/Users/joshm/Projects/igneum-wt-ship0315/docs/plans/release-0.3.15.md` (the newest release notes; `release-0.3.15.md` does not exist on `horizon` or `master`), `/Users/joshm/Projects/igneum-wt-wallet*` (wallet 0.1.4 source, UI 3 branch), `/Users/joshm/Projects/igneum-wt-gpu-fleet/tools/fleet/`, `/Users/joshm/Projects/igneum/vendor/igneum-node-0310/consensus/src/processes/finality.rs` (the node's `finality_reason`).
|
||||
|
||||
Run (nothing live, nothing built): `node --test app/igneum-app/ui/*.test.mjs` (35 pass), `node --test relay/test/*.test.mjs` per file (24 pass), `node --test tools/community/discord-hooks.test.mjs` (31 pass, every live poster stubbed), one `curl -s https://igneum.network/` (the allowed single fetch; saved to the scratchpad), and one read-only SQL pass over the observer's Neon tables (`live_checkpoints`, `live_events`, `live_state`) through the same HTTPS SQL endpoint `site/api/_neon.mjs` uses, to see what every surface had to render during tonight's pause. No browser automation.
|
||||
|
||||
Comparator claims are from memory unless a repository or page is named, and are labelled approximate.
|
||||
|
||||
## 1. The ten rows the project lead will notice first
|
||||
|
||||
| Rank | Id | Row | File or screen | Severity | Hours | Owner | Who it hits | Gate |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 1 | Q1 | The pause has no cause anywhere. The node reports `finality_reason` (`active`, `window filling, N of M`, `paused`) but not the frozen-table share that held tonight's pause; the observer drops even the reason; the API has no reason field; `/live` computes "N% of weight silent" from the sliding table, which read 11 percent silent at 20:05Z while finality was paused (held by the table frozen at lock 6842, 57.3 percent signing). Add `finality_reason`, `held_by` (frozen index, its signing share, its expiry DAA) and lane 3's `finality_provisional` end to end | `vendor/igneum-node-0310/consensus/src/processes/finality.rs:1794-1807` (reason exists, no frozen share), `tools/observer/observer.mjs:720` (copies `finalityActive` only), `site/api/live.mjs:114-122` (no reason), `site/live.html:520` (the silent percentage) | the project lead-visible | 4 (node 1.5, observer and API 1, `/live` and `/api/stats` copy 1, spec 3.9 row 0.5) | consensus-engineer (node), site owner (observer, API, page) | holder, exchange: the one line that says whether a pause is a silent third, a split, or a table waiting to expire; miner: nothing lost, but the app can finally explain itself; operator: a pause with a cause and an end time | A forced pause on Devnet 2 (one slice over a third stopped): `/api/live.finality` carries `reason`, `held_by`, `provisional`; `/live` reads "finality paused since 18:40Z: 89% of the sliding weight is signing, held by the table frozen at lock 6842 (57% signing) until DAA 216,402 (about 20:40Z)"; the exchange guidance in spec 3.9 carries the provisional row (lane 3 proposal 4) |
|
||||
| 2 | Q2 | Ember never says "paused". The engine keeps only `last_lock`, `last_lock_at`, `age_s`, `votes`; the node line showed "#6842 · 1 h ago" under "a point the miners agreed can never be undone" for the whole two-hour pause. No `finality_active` anywhere in the app | `app/igneum-app/src/state.rs:247-248`, `src/engine.rs:3495-3497, 3896-3902` (lock parsed from the miner's `LOCK checkpoint` line only), `ui/app.js:1350-1351` | the project lead-visible | 3 | app owner (engine reads `getFinalityCheckpoints` or the miner's `FINALITY` line, spec 3.9 row; UI state and view test) | home miner and rig: the app tells them finality is paused and why, instead of a lock age that climbs; pool user: the same on the pool page later | Mock state with `finality_active:false, reason:"paused"` renders "Finality paused since 18:40Z, 89% of the 30-day weight signing, held by the frozen table until about 20:40Z" on the node line and in the pill; `view.test.mjs` case; seen on the Devnet 2 forced pause |
|
||||
| 3 | Q3 | A fresh machine's first 60 seconds start with a warning. macOS: ad hoc signature, no Developer ID, no notarisation; the engine strips `com.apple.quarantine` from its own bundle on start; the README's "Right-click > Open" bypass is gone on macOS 15 (approximate). Windows: installer and exes unsigned, SmartScreen "Windows protected your PC", then a UAC prompt for the firewall rule 20 to 50 s in before any screen explains it | `packaging/mac/build-dmg.sh:118-122`, `packaging/mac/README.md:51-53`, `app/igneum-app/src/main.rs:78`, `packaging/windows/README.md:76, 111`, `packaging/windows/build-installer.ps1:188`, `app/igneum-app/src/ota.rs:981-1015` | the project lead-visible | 6 (Developer ID signing plus `notarytool` and stapling in `build-dmg.sh` 3; Authenticode `signtool` step in `windows.yml` and `build-installer.ps1` 2; firewall step after `setup_done` with a sentence on the Cards screen 1), plus the project lead: an Apple Developer account and an OV or EV code-signing certificate (purchases, hours of the project lead's time, approximate) | app owner; the project lead (the certificates) | every new miner on every tier, Windows and macOS: Signal's and Tailscale's installers open with no warning (approximate); today ours open with two | Fresh macOS 15 VM: the DMG's app opens on double-click, `spctl -a -vv` says accepted and notarised; fresh Windows 11 VM: no SmartScreen interstitial, `signtool verify /pa` passes; the first UAC prompt appears after the Cards screen names it |
|
||||
| 4 | Q4 | Every update is "urgent" once an activation height is behind the node: `fork_is_close(Some(198000), 209000)` is true, so the manifest's stale `activation 198000` turned the 0.3.14 update into "the node is 0 blocks away. Installing now", stripped Later and skipped every safe-moment guard (the PC 1 install under a measurement job at 17:52:54Z). The same path has no finality input: an update applies through a pause | `app/igneum-app/src/manifest.rs:350-355` (`daa + 1800 >= h`), `:322-347` (`safe_to_apply`, no finality), `src/ota.rs:484`, `ui/app.js:217`, `docs/plans/release-0.3.14.md:79`, `release-0.3.15.md` section 6 | the project lead-visible | 3 (close means within 1,800 blocks ahead and not behind 1; the publisher drops a passed `activation_height` 0.5; `Moment.finality_paused` holds a non-urgent update 1.5) | app owner | home miner, rig: no surprise restart mid-measurement or mid-pause; operator: an activation that has passed is not an emergency | Unit tests: behind by any amount is not close; `publish-manifest.sh` refuses a passed height; a paused mock holds the update with the words "finality is paused; installing when it resumes"; the 0.3.16 manifest carries no stale height |
|
||||
| 5 | Q5 | The homepage says "final" while finality is paused, in a 90-word hero, under a share card that renders as a thumbnail. The chain scene takes `src.locked` and draws the dashed line labelled "final" at the newest locked block with no `finality.active` check (R4.6.3 was fixed on `/live` only); the hero is one 90-word paragraph with two bench deep links and "ships when the packaging row lands"; the miner section still says "a 24 GB NVIDIA card proves as well" while the hero and litepaper say 8 GB; home, litepaper, live and explorer share a 256 px `og-small.png` with `summary` cards | `site/index.html:810, 838` (final label), `:345` (hero), `:494` and `site/miner.html:7, 13, 21, 265, 382` (24 GB), `site/index.html:15-19` (OG) | the project lead-visible | 3 (final label 0.5, hero 1, 24 GB sentence 0.5, four 1200x630 cards 1) | site owner | every visitor; a holder or exchange reading "final" during a pause is the worst of them | No "final" label while `finality.active` is false (the `/live` rule); hero under 40 words with one link; one proving-tier sentence on every page; every page's shared card is 1200x630 |
|
||||
| 6 | Q6 | The hub shows stale data as live, said "active" for the first 20 unlocked checkpoints, says "paused" with no since or cause, and buried the pause under 401 miner_quiet and miner_back events. After the first load an API failure only changes the header to "offline: ..."; tiles, cards and "Refreshed" keep the old values. `finality_active` flips only after `presence_window` (20 on the devnet) indices without a lock, so 18:42 to 18:50Z read "active" in white with no lock forming. No `finality_paused` or `finality_resumed` event exists; `live_events` between 18:25Z and 21:30Z holds 203 `miner_quiet`, 198 `miner_back`, 55 `difficulty`, 30 `checkpoint_locked` (the last at 18:42:10Z) and no pause row. The checkpoint table also showed "% of active" above 100 (102.4 to 113.4 percent on indices 6900 to 6930) | `relay/ui.html:564` (stale), `:367-368` (tile), `relay/api/console.mjs:214`, `vendor/igneum-node-0310/consensus/src/processes/finality.rs:1794` and `vendor/igneum-node/consensus/core/src/finality.rs:63, 75` (`presence_window` 240 mainnet, 20 devnet), `tools/observer/observer.mjs:652` (the only finality event), `live_checkpoints.fraction_active` rows 6900 to 6930 | the project lead-visible | 4 (dim every panel with "last data N min ago" 1; amber on the first under-2/3 checkpoint 0.5; `finality_paused` and `finality_resumed` events, and collapse quiet/back pairs under 5 min 1.5; clamp or explain the active fraction 1) | fleet agent (hub), consensus-engineer (observer events, the fraction) | operator: the hub is the project lead's first screen; a holder reading the public API gets the same events | Cut the network: every hub panel dims within 15 s; a forced Devnet 2 pause turns the tile amber on the first checkpoint under two thirds, posts one `finality_paused` event with the cause and one `finality_resumed` with the duration; no fraction above 100 percent on any row for 24 h |
|
||||
| 7 | Q7 | Discord said nothing. The bot has no credentials file and its timer is not installed, so tonight's pause produced zero posts; had it been live, a condition already firing at the watcher's first look is marked `preexisting` and never opens an incident; the pulse hides the pause in its description with no colour or title change | `docs/community/discord-hooks.md:81-82`, `tools/community/discord-hooks.mjs:481-485`, `:252-255`, `:158`, `infra/build-server/discord-hooks/install.sh` | the project lead-visible | 3 (install and one live pulse 1; a pre-existing condition opens with "since at least <first look>" 1; paused pulse in a distinct colour with a title suffix 1) | miner-community-lead (community owner) | every Discord reader, which on launch day is every miner | `check` prints three "set"; one live pulse in #numbers; a fixture where the pause predates the first tick opens an incident; the paused pulse renders amber with "(finality paused)" in the title |
|
||||
| 8 | Q8 | The relay's security fixes are not on master. Commit 28c028b (X23 to X28: `relay/lib/guard.mjs`, `handler.mjs`, run signatures, retention) is on `fud-close` and the `ledger-*` branches only; `git merge-base --is-ancestor 28c028b master` is false. On this tree any of the three intake keys (one ships in every miner app) can post, sync and delete console items, read the whole feed, and every client puts the token in the URL path | `relay/api/console.mjs:249, 283-301`, `relay/lib/relay.mjs:34-46`, `relay/clients/agent.sh:10`, `igneum-agent.ps1:13, 72, 154`, `tools/relay.mjs:24` | operator-visible (security) | 2 (merge or cherry-pick with the 47 relay tests 1; handler test that an intake key gets 403 on post, sync, delete 1) | fleet agent (relay owner) | operator: the console is the control plane for every PC and the fleet; a miner app's intake key is in every install | `28c028b` is an ancestor of the deploying branch; 47 relay tests pass; the handler test above passes; `relay/README.md` names the deployed commit |
|
||||
| 9 | Q86 | The fleet page is blind to the standing fleet, to death and to finality. `page.py:43-47` publishes `standing` and `devnet2` and the page references neither (grep 0); a dead box reads "running" for up to 20 minutes because state comes from `boxes.json` and `standing.jsonl` is never read; no fleet or console script reads `finality_active`, so tonight's pause was found by hand. The night the project lead ruled that 22 boxes stay up permanently, the page that shows them cannot show them | `igneum-wt-gpu-fleet/tools/fleet/page.py:38-47`, dlsite `index.html:243`, `lib/standing.py:54-56`, `tools/console.mjs:124` | the project lead-visible | 6 (standing block with USD per day and the Devnet 2 gate line 2; "unreachable since HH:MM" within 2 minutes 2; finality on the page and in `console.mjs chain` 2) | fleet agent | operator (the project lead reads this page every evening); every tier indirectly: a dead standing box is weight that left silently, the class of tonight's pause | the page shows the standing count and spend; a box killed by hand reads "unreachable since" within 2 minutes; a forced Devnet 2 pause reads on the page and in the console |
|
||||
| 10 | Q9 | The wallet page describes 0.1.4 while UI 3 (five state words, light mode, pounds line) sits unmerged on `wallet-ui-3`; the MetaMask guide's testnet RPC is still labelled a placeholder, its devnet RPC port is the miner's node (26790) while a wallet-only Mac runs 26800, and `wallet_addEthereumChain` carries `blockExplorerUrls: []` and no `iconUrls`, so MetaMask shows no explorer link and a blank icon | `site/wallet.html:261-266, 312`, `igneum-wt-wallet-ui/docs/plans/wallet-ui-3.md:93, 112`, `site/metamask.html:197, 225, 233, 285-286`, `igneum-wt-wallet/app/igneum-wallet/src/node.rs:24-26` | the project lead-visible | 6 (wallet 0.1.5 with UI 3 published and the page rewritten 4; guide: both ports, the placeholder line removed, explorer URL and icon in the request 2) | app owner (wallet), site owner (guide) | holder: the first non-miner product; a wrong port or a placeholder RPC is a dead end at the first step | `downloads.json` wallet-mac 0.1.5; the page names pending, included, executed, proven, finalised; `eth_chainId` returns 0x116e from the guide's URL; MetaMask shows the explorer link after the add-chain button |
|
||||
|
||||
Next in line, outside the ten: Q10 (the explorer knows nothing about finality and swallows failures after first load, 3 hours), Q80 (the Windows installer pipeline is dead on GitHub billing) and Q12 (Ember's light mode fails contrast on the private key and every live state).
|
||||
|
||||
Hours for the ten: 40 agent hours, plus the project lead's certificate purchases for Q3.
|
||||
|
||||
## 2. Tonight's pause on every surface (the ledger row the project lead asked for)
|
||||
|
||||
Times UTC. The chain-side facts are lane 3's (`finality-and-weight.md` 3.1) and the observer's own rows, read tonight: last lock 6842 at about 18:39:40Z; 6843 proposed at 53.1 percent of total; `finality_active` false from about 18:50Z (index 6862, twenty indices without a lock, `presence_window` 20); no lock after 6842 as of 20:05:03Z (`live_state.updated_at`), `total_weight` 7,167, `active_weight` 6,346 (88.5 percent signing on the sliding table), `voters` 85, and no `finality_reason` key in the stored JSON. Sampled `live_checkpoints` rows:
|
||||
|
||||
| Index | First seen | State | Signed, percent of total | Signed, percent of active | Votes |
|
||||
|---|---|---|---|---|---|
|
||||
| 6820 | 18:28:30 | locked | 88.8 | 100.0 | 80 |
|
||||
| 6840 | 18:42:10 (ingested after the hub came back) | locked | 79.5 | 98.6 | 83 |
|
||||
| 6850 | 18:43:41 | proposed | 55.2 | 76.8 | 75 |
|
||||
| 6880 | 19:00:22 | proposed | 50.1 | 81.2 | 49 |
|
||||
| 6910 | 19:13:55 | proposed | 67.4 | 107.4 | 55 |
|
||||
| 6920 | 19:19:07 | proposed | 74.8 | 113.4 | 78 |
|
||||
| 6960 | 19:40:19 | proposed | 86.0 | 100.0 | 78 |
|
||||
| 7010 | 20:04:32 | proposed | 87.7 | 99.1 | 73 |
|
||||
|
||||
From 6910 (19:13:55Z) every proposed checkpoint carried over two thirds of the sliding total and did not lock: the frozen table of Q5 held it (lane 3, 3.1). No surface could say so, because no field carries it. The "percent of active" above 100 on 6900 to 6930 is a display bug on the hub's checkpoint table (the active denominator lags the signers).
|
||||
|
||||
| Surface | What it showed 18:40Z to 20:05Z (from the code path and the rows above) | Honest? | Fix row |
|
||||
|---|---|---|---|
|
||||
| Ember (`app.js:1350`) | "last lock #6842 · 1 h 25 min ago" with "a point the miners agreed can never be undone; locks at 2/3 of the 30-day weight". No pause word anywhere | No: the age climbed and the note contradicted the state | Q2 |
|
||||
| Site `/live` (`live.html:512-520`) | Status LIVE, blocks flowing, finality bar grey, "finality paused: 45% of weight silent" at 18:50Z falling to "11% of weight silent" by 20:05Z, no final region, no checkpoint ticks; "last lock #6842, 1 h ago" in the stats (`:404`, no active check); a hovered checkpoint block still read "locked" (`:450`) | Half: the pause is named (R4.6.3) but the percentage says the opposite of why it is paused once the stayers passed two thirds | Q1 |
|
||||
| Homepage (`index.html:810, 838, 879`) | Counter "last lock: paused"; the chain scene still drew the dashed line labelled "final" at the newest locked block | No: "final" and "paused" on one screen | Q5 |
|
||||
| Explorer (`explorer.html`, `block.html`) | Identical to a normal hour; stars on locked checkpoint rows; block pages of ordinary blocks "not a checkpoint" | No: nothing marked the pause | Q10 |
|
||||
| Hub (`relay/ui.html:367-368`) | 18:30 to 18:42Z: "observer STALE N s" in red, tiles frozen, finality "active" (the hub itself was down with the Mac). 18:42 to about 18:50Z: "active" in white, no lock forming. About 18:50Z onward: "paused" in amber, "last lock #6842" in amber, eight rows "proposed" at 53 to 89 percent, Events card full of `miner_quiet` and `miner_back` | Half: paused, but no since, no cause, no end; active for ten minutes of no lock | Q6 |
|
||||
| Discord | Nothing: the bot is not live. Had it been: the 20:00Z pulse would have read "Finality paused since 2026-10-06 18:4x UTC: nothing is final until two thirds of the 30-day weight signs again; mining and proving are paid as usual" in the description, and the watcher would have marked the pause `preexisting` and never opened an incident | No | Q7 |
|
||||
| Wallet 0.1.4 (`igneum-wt-wallet` `app.js:185, 340`) | Node card "paused" when a local node reports `finality_active` false, or "not checkpoint-checkable without a node" on the public RPC; transaction rows stay "in block N" with no mention | Half | Q35 |
|
||||
| Pool page (`pool/web/index.html`) | Not deployed; the code has no finality concept; it would have confirmed and paid on blueness throughout | n/a (a design choice, stated in Q50) | Q50 |
|
||||
|
||||
The row for the ledger: **Q1 plus Q2, Q5, Q6, Q7, Q10**: every public and operator surface either hid the pause, named it without a cause, or contradicted it, for two hours, during the first finality pause the project has had in public. The gate across all of them is one forced pause on Devnet 2 with a screenshot of each surface, before the public testnet opens.
|
||||
|
||||
## 3. Per system
|
||||
|
||||
### 3.1 Miner app (Ember)
|
||||
|
||||
**What it does today.** A Rust engine (`app/igneum-app/src/`) runs `igneumd` and one GPU worker per card, serves a tokenised dashboard on 127.0.0.1 (`src/server.rs:11-33`, routes `:184-359`: `api/state`, `api/log`, `detect`, `setup`, `key/saved`, `key/reveal`, `start`, `pause`, `resume`, `cards`, `tune/goal`, `settings`, `prove`, `update/check|install|auto|open`, `jobs/allow|check`, `power/apply|control`, `sweep/*`, `clock/*`, `quit`). Screens (`ui/index.html`): Welcome (line 71), Cards (102), Address (127), the one-time key sheet (438), then a rail with Mine, Earnings, Prove, Settings (164 to 413), a log drawer (460), a status strip (`Notices`, `ui/app.js:7-140`) and an update card (`UpdateCard`, `app.js:154-243`). Ember Tune (`src/ember.rs`, `src/sweep.rs`, `docs/plans/ember-tune.md`) tunes power and clocks per card against a goal. Updates are Ed25519-signed manifests (`src/ota.rs`, `src/manifest.rs`). Hosts: `app/mac/IgneumMiner.swift` (WKWebView, menu bar), `app/windows/host.cpp` (WebView2, tray). The shipped build is 0.3.14 with miner-ui-3; 0.3.15 (miner-ui-4: the big-number hero and the chain scene) is staged, its canary failed on block version 1026 and was rolled back (`release-0.3.15.md` section 5). UI tests: 35 pass.
|
||||
|
||||
**Comparators and the exact gap** (all approximate, from memory of the products).
|
||||
|
||||
| Comparator | Their screen or feature | Ours, and where it falls short |
|
||||
|---|---|---|
|
||||
| NiceHash QuickMiner | Profitability per day in fiat on the main screen; Rig Manager web view of your own rigs | Earnings reads "£0.00 earned: nothing is bought or sold on devnet" (`index.html:250`); mined IGN is never shown, only proving `paid_wei` (`app.js:1364`); no owner-facing remote view (the console is the operator's) |
|
||||
| HiveOS | Hash rate and temperature charts over hours; Telegram or Discord alert on a GPU fault or a rig offline; per-GPU fan and memory clock | A 10-minute blocks strip only (`app.js:1052-1127`); no outbound alert; manual control is the power slider (`app.js:1255`); the memory knob exists in the engine with no control |
|
||||
| lolMiner, T-Rex consoles | Per-GPU accepted, rejected, invalid and faults on every status line | Per card only "N blocks" (`app.js:306`); `rejected_session` a total in the hero sub-line (`app.js:1290`); `mismatched=` and `faults=`, which X21 made the engine read, never reach the row |
|
||||
| Nanominer | Restart-on-hash-drop watchdog with a visible threshold | `src/watchdog.rs` exists; the row says "stopped after repeated faults" (`app.js:296`) with no count or threshold |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q2 | Finality pause invisible (section 1) | `state.rs:247`, `engine.rs:3495`, `app.js:1350` | the project lead-visible | 3 | app owner | section 1 |
|
||||
| Q4 | Every update urgent once the activation is behind; no finality hold (section 1) | `manifest.rs:322-355` | the project lead-visible | 3 | app owner | section 1 |
|
||||
| Q11 | Offline start says "The update failed." A check error with no held manifest calls `set_error`; the cached manifest is loaded for the floor but never into `self.manifest`; the notice offers "Try again", which POSTs install | `ota.rs:649-654, 195-200`, `app.js:29, 1198` | user-visible | 1 | app owner | notices test: a check-class error renders "Could not check for updates" with no install action |
|
||||
| Q12 | Light mode fails contrast on every live text: molten `#B8731F` on white 3.81:1, on bone 3.38:1; ember `#E04A14` on bone 3.61:1; molten carries the private key, the address and the live states. `miner-ui-3.md` section 9 claims 4.5:1 everywhere | `app.css:29, 263, 338, 431` | user-visible | 1 | app owner | a node script over the token pairs asserts 4.5:1 for every text token in both schemes; CI runs it |
|
||||
| Q13 | Per-card rejects and faults never reach the row (the lolMiner line) | `app.js:291-309, 1290` | the project lead-visible on PC 1 | 2 | miner-community-lead | row meta "N rejected · M faults" when nonzero; view test |
|
||||
| Q14 | Windows first run raises a UAC prompt (firewall rule) before any screen explains it (part of Q3) | `ota.rs:981-1015`, `index.html:71-99` | user-visible | 1 | app owner | the firewall step runs after `setup_done`; the Cards screen names it |
|
||||
| Q15 | Mac first-run instruction stale: "Right-click > Open" (part of Q3) | `packaging/mac/README.md:51`, `site/miner.html` download copy | the project lead-visible on a fresh Mac | 1 | app owner | the download page and README name the Privacy and Security step until the certificate lands |
|
||||
| Q16 | No rate history: a 10-minute strip against HiveOS's hours | `app.js:1052-1127` | user-visible | 4 | app owner | one-hour per-card sparkline from an engine ring buffer; view test |
|
||||
| Q17 | Hill climb hidden until `tune_climb` is on the state | `index.html:341`, `app.js:1407` | operator-visible | 1 | app owner | the row shows whenever the engine answers `tune/goal` |
|
||||
| Q18 | Earnings never shows mined IGN; "£0.00 earned" is the first number on the tab | `index.html:250`, `app.js:1364` | user-visible | 2 | miner-community-lead | the tab shows blocks mined and the subsidy they earned in IGN, with the devnet line under it |
|
||||
| Q19 | 10 px type in five places, 9 px ruler | `app.css:196, 271, 272, 302, 491, 531` | cosmetic | 1 | app owner | no `font-size` under 11 px except the ruler |
|
||||
| Q20 | Design screens in `docs/design/app-screens/*.png` are a UI 1 app (v0.3.0 tiles, a Finality card the shipped UI lacks) | `docs/design/app-screens/` | cosmetic | 1 | app owner | screens regenerated from `?screen=` on the current build |
|
||||
| Q21 | AMD step line prints a percent as watts ("owed: a unit word for AMD") | `ember-tune.md:278` | operator-visible | 1 | miner-community-lead | the line reads "70% (an offset)"; unit test |
|
||||
| Q22 | A code comment naming the founder ships in the UI bundle (`// The list is ordered by performance (the project lead, 6 October 2026)`), and the ui bundle is outside every forbidden-string check | `app/igneum-app/ui/app.js:270`, `tools/ci/identity-check.sh` | cosmetic (identity rule) | 0.5 | app owner | `tools/ci` forbidden-string check covers `app/igneum-app/ui/`; the comment reads "(ruling of 6 October 2026)" |
|
||||
| Q23 | Two copy-law slips: "Votes lock the chain; leave it on." (aphorism), "The window can close. The miner keeps going" (two-beat) | `index.html:404, 81` | cosmetic | 0.5 | app owner | reworded; the grep below stays at 0 em dashes |
|
||||
|
||||
**Error states, exact strings.** Engine down (four failed polls): pill "engine away" (`app.js:1437`); node line "Node: no answer", sub "the engine is not answering; the window reconnects by itself"; Mac host "The engine stopped. Quit and open Igneum Miner again." (`IgneumMiner.swift:233`); Windows "The engine stopped. Close this window and open Igneum Miner again." (`host.cpp:301`). Node down: "the node could not start" or the engine message, "the node is not running", "the external node went away" (`app.js:452-453`, `engine.rs:3163`); pill "node failed" (`app.js:1428`); digest box "the node is not running" (`app.js:1341`); canvas "waiting for the node to sync" (`app.js:1099`). Finality paused: nothing (Q2). Update server unreachable: "The update failed. Manifest signature: <curl error>." (`app.js:1360`, Q11). GPU lost: "Card removed: <name>. Its worker stopped." (`app.js:95`), row word "removed", sub "unplugged; its worker stopped. The row goes in five minutes." (`app.js:303`); Code 43: "<name>: not usable (Code 43). No worker runs on it." with "reboot with the card attached; if it persists, reinstall the driver with the card attached" (`app.js:100`, `detect.rs:55`).
|
||||
|
||||
**Already better than the comparators.** Automatic efficiency tuning against a goal with the money consequence on screen (`app.js:408-419`; 37 to 41 percent more hashes per watt measured on PC 1, `ember-tune.md:254, 271`). Signed, hash-checked, rolled-back updates with a safe-moment rule and machine slots (`ota.rs`, `manifest.rs`). A reason word on every zero rate and hot-plug words (`ember-tune.md` 6a). A virtualised log drawer with time jump and error marks (`app.js:880-1050`). Confirmations in place, no dialogs. A tokenised local API with origin checks (`server.rs:102-125`). Dark-scheme contrast is good: bone 17.4:1, ash 7.0:1, ember 5.6:1, molten 11.0:1 on obsidian (`app.css:19`). Focus rings, `aria-live` strip, labelled switches and sliders, reduced motion honoured on every animation (`app.css:67, 113, 142, 148, 157, 420, 437, 466`; `app.js:1137`). None of the four comparators ship the first two.
|
||||
|
||||
### 3.2 Wallet
|
||||
|
||||
**What it does today.** One desktop wallet (Igneum Wallet, Rust engine plus a local window; `igneum-wt-wallet/app/igneum-wallet/`: `engine.rs`, `vault.rs`, `hd.rs`, `tx.rs`, `finality.rs`, `node.rs`, `updater.rs`, `server.rs`; ui `index.html`, `app.js`, `lock-screen.js`, `update-card.js`), shipped as 0.1.4 (`site/downloads.json` wallet-mac, 19.6 MB; `release-0.3.15.md:90`). 0.1.5 (bundled 0.3.14 node, RPC on localhost) has a DMG and is unpublished (`release-0.3.14.md:82`). UI 3 is commit ab8dfdc on `wallet-ui-3` and `wallet-0.1.5`, not on master. No wallet inside the miner app (it holds the payout key, `wallet.json`, and a button "Open the wallet" to the site, `app/igneum-app/ui/app.js:826`). No web wallet. A MetaMask guide (`site/metamask.html`), an address page (`site/address.html`, noindex, balance plus blocks mined), a marketing page (`site/wallet.html`). Keys: BIP-39 (12 to 24 words, `hd.rs:23`), BIP-44 Ethereum path; vault Argon2id 64 MiB, 3 passes, XChaCha20-Poly1305 (`vault.rs:14-81`); key zeroised on lock (`engine.rs:392`); 24 words shown once, three typed back (`engine.rs:321`); raw key export behind password or Touch ID (`ui/index.html:271-275`). Chain ids 4461, 4462, 4463 (`metamask.html:233`). Signing: EIP-1559 value transfers only; no EIP-712, no `personal_sign`, no contract calls, no dapp provider, no hardware wallet (spec 8.5 item 2 decided, not built; `docs/fud-ledger.md:2058`).
|
||||
|
||||
**Comparators and the exact gap** (approximate unless a standard is named).
|
||||
|
||||
| Comparator | Their feature | Ours |
|
||||
|---|---|---|
|
||||
| MetaMask | Account list and HD derivation; address book; speed up and cancel a pending tx; `window.ethereum` provider; `eth_signTypedData_v4` | One account (`engine.rs:343` refuses a second import); an address field only (`ui/index.html:177`); no replace-by-fee; no provider; no EIP-712 |
|
||||
| Rabby | Pre-sign simulation and recipient risk flags | Review shows amount, fee and total (`app.js:293-295`) |
|
||||
| Frame | Ledger, Trezor, GridPlus signers; injected provider for any browser | Neither (spec 8.5 "MUST offer", no code) |
|
||||
| Monero GUI | View-only wallet; subaddresses; a settable node address with status | Node source engine-chosen, no settable endpoint (`wallet-ui-3.md:112`) |
|
||||
| KDX, Kaspium | Sync bar with peer count; per-transaction DAA score | "reading the chain, N of M blocks" (`app.js:175`), no peer count |
|
||||
| EIP-1193 | `request` provider | Used on the guide (`metamask.html:294`) |
|
||||
| EIP-6963 | Multi-provider discovery | Absent; the guide falls back to whichever extension owns `window.ethereum` (`:292`) |
|
||||
| EIP-3085 | `wallet_addEthereumChain` with `blockExplorerUrls`, `iconUrls` | `blockExplorerUrls: []`, no icon (`:225, 285-286`) |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q9 | Page vs UI 3; guide placeholders; empty explorer URL (section 1) | `site/wallet.html`, `site/metamask.html` | the project lead-visible | 6 | app owner, site owner | section 1 |
|
||||
| Q30 | Private key export copies to the clipboard with no clear and no timer | `igneum-wt-wallet/.../ui/app.js:129, 463` | user-visible | 2 | app owner | clipboard cleared after 60 s by the host; unit test on the timer |
|
||||
| Q31 | Wallet OTA trusts one key; no `revoked_keys`, unlike the miner's updater | `docs/plans/consequences-2026-10-05.md:36`, `updater.rs:7, 729` | operator-visible | 3 | app owner | updater carries `OTA_PUBLIC_KEYS [K1, K2]` and honours `revoked_keys`; the three-key test of the rig installer |
|
||||
| Q32 | No hardware wallet path despite spec 8.5 "MUST offer" | `docs/spec/08-client-security.md:36` | the project lead-visible | 24, or 0.5 to relabel | app owner (cryptographer reviews) | a Ledger signs one devnet transfer through the Ethereum app at chain id 4463; or the spec row reads "Designed, not shipped" |
|
||||
| Q33 | Single account, no second address, no address book | `engine.rs:343`, `ui/index.html:177` | user-visible | 6 | app owner | two derived accounts, a saved recipient reused in Send |
|
||||
| Q34 | `confirm()` on Remove wallet never renders in the Mac host | `app.js:598`, `wallet-ui-3-audit.md:189` | user-visible | 1 | app owner | in-page two-step card; snapshot test |
|
||||
| Q35 | Finality pause: node card "paused" or "not checkpoint-checkable without a node", rows stay "in block N"; public-RPC mode never says what to do | wallet `app.js:185, 321, 340` | user-visible | 2 | app owner | the row reads "in block N; finality is paused on the network" and the public-RPC line names the fix with a button |
|
||||
| Q36 | Reorged-out transaction: "in a block" then the row vanishes; no resend hint | `app.js:340`, `docs/fud-ledger.md:2154-2158` | user-visible | 3 | execution-engineer (node side), app owner (the row) | a transfer unwound in the P23 conformance run shows "reorged out, resending" and ends executed |
|
||||
| Q37 | The address page shows no finality state and "n/a" with a bare reason for balance | `site/address.html:205-209, 242-252` | cosmetic | 2 | site owner | the page shows the latest locked checkpoint and whether finality is active |
|
||||
| Q38 | The permanent phishing sentence differs between surfaces ("words or your key" on the wallet, "your key" in the miner) against spec 8.5's one verbatim sentence | `site/wallet.html:366`, wallet `ui/index.html:54`, `app/igneum-app/ui/index.html:97`, `08-client-security.md:39` | cosmetic | 0.5 | site owner, app owner | one sentence, grep-identical on all three |
|
||||
| Q39 | Vault file permissions not set explicitly (no `0o600` in `vault.rs`) | `vault.rs` | operator-visible | 0.5 | app owner | the file is created 0600; test |
|
||||
|
||||
**Error states.** Node down: pill "no node", balance note "waiting for a node" (`app.js:160, 176`), the node line's raw engine message (`:186`; UI 3 replaces it with "the node stopped answering; trying again", `wallet-ui-3.md:199`). Pending versus final: "PENDING, waiting for a block", "IN A BLOCK, chain block N; final once a verified checkpoint covers it", "FINAL, under checkpoint N, certificate verified by this wallet", "FAILED, the execution failed; the fee was still paid" (`app.js:340`). Address page: "The API did not answer: <message>" (`address.html:270`), "Not found." (`:247`). MetaMask page: "No Ethereum wallet found", "Waiting for the wallet...", "Cancelled in the wallet.", "The wallet refused: <message>" (`metamask.html:292-296`).
|
||||
|
||||
**Security notes.** Plaintext key only in engine memory, zeroised on lock and after each sign (`engine.rs:392, 563`); the window never holds the key (nonce-gated reveal, `server.rs:108`); local API on 127.0.0.1 with a random port, per-run token path and a host token (`server.rs:1, 39, 71`); chain id checked at quote and send (`engine.rs:549`). Testnet RPC is https (`testnet-go.md:13`); devnet is http to 127.0.0.1. Chain ids 4461 to 4463 were absent from chainid.network on 3 October (`docs/design/execution-layer.md:354`), not yet registered (`frontier.md:645`); from memory no listed chain uses them (approximate).
|
||||
|
||||
**Already better.** The wallet verifies finality certificates itself with the node's consensus code (`finality.rs:101-121`) and never trusts the RPC's "locked"; MetaMask and Rabby trust the RPC's block tag (approximate). Touch ID is bound to the exact quote (address, value, nonce, chain id) with a 30 s one-use nonce (`engine.rs:539`). Argon2id at 64 MiB is above MetaMask's PBKDF2 default (approximate). The QR is drawn locally as SVG with the `ethereum:` URI and a checksummed address (`server.rs:140`). The MetaMask page says plainly that nothing on it asks for a seed (`metamask.html:172`).
|
||||
|
||||
### 3.3 Hub and relay
|
||||
|
||||
**What it does today.** One Vercel project (`igneum-relay`, Neon database, Blob store; `relay/README.md:3-5`). `relay/ui.html` (583 lines) is the console at `/r/<token>/` (`relay/vercel.json:5-8`); `relay/index.html` is a placeholder. Functions: `relay/api/relay.mjs` (feed, items, files, inbox, register, name, role), `relay/api/console.mjs` (machines, jobs, builds, chain, log, results, tuning), `relay/api/wake.mjs` (public long-poll for the apps' jobs file, 30 a minute per IP, `relay/lib/wake.mjs:15, 42-54`). Auth: the 20-character token in the path or `x-relay-token`, or any of `RELAY_KEY`, `LOG_INTAKE_KEY`, `LOG_INTAKE_KEY_NEXT` in `x-igneum-key` (`relay/lib/relay.mjs:34-46`); only `task`, a `run` or `task` drop, `name`, `role`, `delete` demand the token (`relay/api/relay.mjs:114-115`). Two job models: relay `run` items polled every 20 s by `relay/clients/igneum-agent.ps1:165` and executed as administrator; and signed app jobs in `igneum-jobs.json`, whose status the console derives from `miner_logs` rows (`relay/api/console.mjs:172-188`). Seven tabs (`relay/ui.html:186-192`): Machines, Jobs, Builds, Chain, Work log, Results, Relay; refreshed every 15 s when visible (`:577`), server cache 10 s (`console.mjs:21`). Chain tiles: blocks, identities, hash estimate, difficulty, last lock, finality, peers, blocks per second (`:363-370`). Mac tools: `tools/relay.mjs`, `tools/console.mjs`, `tools/jobs.mjs`. Tests: 24 pass per file (`node --test relay/test` as a bare directory fails under Node 22.23.2 with MODULE_NOT_FOUND; the README's command needs the glob).
|
||||
|
||||
**Comparators and the exact gap** (approximate).
|
||||
|
||||
| Comparator | Their screen | Ours |
|
||||
|---|---|---|
|
||||
| Tailscale admin console, Machines | Last seen, OS and version, key expiry with "Disable key expiry", tags; an ACL editor with tests | Last seen and a red card after 180 s (`relay/ui.html:270, 273`); no credential state (one static token, `relay/lib/relay.mjs:36`), no per-machine key, no revoke; roles are labels never checked (`relay/api/relay.mjs:191-195`) |
|
||||
| GitHub Actions job view | Live streaming log with line anchors, per-step timing, re-run, artefacts, annotations at the top | One text blob in a modal (`relay/ui.html:319`); `duration_s` and the error lines that `tools/jobs.mjs:41-44` already parses are discarded by `console.mjs:184-187`; no re-run; results are separate items |
|
||||
| Vercel deploy log | Build steps, deployment list, instant rollback, diff between deployments | Builds tab shows the current manifest, the last CI fetch, 40 posts and a folder listing (`relay/ui.html:323-341`); no rollback, no manifest history, no diff |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q6 | Stale shown as live; "active" for 20 unlocked indices; "paused" with no since or cause; event flood (section 1) | `relay/ui.html:564, 367-368` | the project lead-visible | 4 | fleet agent, consensus-engineer | section 1 |
|
||||
| Q8 | X23 to X28 fixes off master; intake key can write and delete; token in URL (section 1) | `relay/api/console.mjs:249`, `relay/lib/relay.mjs:44` | operator-visible | 2 | fleet agent | section 1 |
|
||||
| Q40 | A hung job reads "running" forever: no elapsed time, no timeout, no "last line N min ago" | `relay/api/console.mjs:182-187`, `relay/ui.html:317` | the project lead-visible | 2 | fleet agent | a killed job reads "no report for 12 min" in red within one refresh |
|
||||
| Q41 | Jobs tab trusts the unsigned `igneum-jobs.json` while the apps verify the signed file | `relay/api/console.mjs:170` vs `tools/jobs.mjs:57-60` | operator-visible | 1.5 | fleet agent | the tab shows the envelope's signature state per file |
|
||||
| Q42 | Job result modal: no error lines first, no duration, no anchors (the Actions view) | `relay/ui.html:319`, `console.mjs:184-187` | the project lead-visible | 3 | fleet agent | a failed run opens with its five error lines and its duration first |
|
||||
| Q43 | Machines: no per-machine key state, no revoke, roles never enforced (the Tailscale view) | `relay/lib/relay.mjs:14`, `relay/api/relay.mjs:191-195` | operator-visible | 3 (after Q8) | fleet agent | a revoked PC's register is 403 and its card says "key revoked" |
|
||||
| Q44 | Builds: no rollback, no manifest history (the Vercel view) | `relay/ui.html:328-341` | operator-visible | 4 | fleet agent | one tap republishes the previous manifest and the fleet's OTA state shows it |
|
||||
| Q45 | "live feed unreachable: live feed unreachable" doubled; the whole Chain tab empties, losing Events and the infra card | `relay/api/console.mjs:210`, `relay/ui.html:357` | cosmetic | 0.5 | fleet agent | live feed down: tiles grey, Events and infra card still render |
|
||||
| Q46 | Replay: `register` and `task` have no nonce; a captured `task` POST re-queues a run | `relay/api/relay.mjs:153-167` | operator-visible (security) | 1 | fleet agent | a replayed `task` is refused; test |
|
||||
|
||||
**Error states.** Relay API unreachable: header "offline: Failed to fetch" and a grey dot (`relay/ui.html:564, :32`); the panel is replaced only while it still shows "loading". Node down: a machine card goes red after 180 s with "silent 4h" (`:270, 273`; `SILENT_S`, `console.mjs:35`); a deliberate stop reads "stopped (quit) 2h ago" (`relay/lib/parse.mjs:134-140`); the live API down reads "live feed unreachable: <error>" (`:357`); the observer stale reads "observer STALE N s" in red after 30 s (`:361`, `site/api/live.mjs:8`) while every tile keeps its last value. Finality paused: `tile('finality', f.active ? 'active' : 'paused', ...)` in amber `#FFB35C` (`:368, :17, :65`); the "last lock" tile amber too (`:367`); eight checkpoint rows with state and fraction (`:374`). Job hang: "running" until a `SUMMARY` with `finished_at` arrives (`console.mjs:179-182`); relay `run` items show "read" and never "done" (`tools/relay.mjs:58`).
|
||||
|
||||
**Security, X23 to X28 on this tree.** X23: the tier split exists (`relay/api/relay.mjs:114-115`), no run signature, no per-machine HMAC, the intake key still writes the console (`console.mjs:249`). X24: every client builds `/r/$TOKEN/api` (`agent.sh:10`, `send.sh:11`, `igneum-agent.ps1:13`, `tools/relay.mjs:24`, `tools/console.mjs:27`). X25: `Arm-Restart` at top level and `/RL HIGHEST` (`igneum-agent.ps1:154, 72`). X26: feed to 500 with no retention; `delete` leaves blobs (`relay.mjs:48, 148-152`); `__DL_BASE__` substituted into bodies (`tools/relay.mjs:79`). X27: `from` is free text, `register` for any hostname (`relay.mjs:30, 153-167`). X28: constant-time compare and HSTS present (`relay/lib/auth.mjs:5-11`, `relay/vercel.json:38-41`); still a GET that acks (`relay.mjs:85`), `RELAY-REBOOT` anywhere in output (`igneum-agent.ps1:133`), no rate limit, `NOPASSWD:ALL` (`wsl-setup.ps1:40`), `sudo -S` with the password (`prover-setup.ps1:21`). All of this is what Q8 merges.
|
||||
|
||||
**Already better.** One phone-first page, no login flow, seven tabs, 15 s refresh (`relay/ui.html:5, 577`); Tailscale, Actions and Vercel each take several screens on a phone (approximate). The machine card fuses miner STATUS, node log, app telemetry and OTA state and remembers an OTA line after it scrolls out (`console.mjs:61-84`); no comparator shows workload telemetry. Stale marking at 180 s drops the card from the total; stopped and silent are distinct words (`parse.mjs:16-21, 134-140`). Drop-anywhere upload direct to Blob with a progress bar and a 50 MB cap (`ui.html:506-534`). The wake long-poll is dependency-free and tested with a fake clock (`relay/lib/wake.mjs`). Headers: nosniff, frame DENY, noindex, no-referrer, no-store, HSTS on every path (`relay/vercel.json:21-43`).
|
||||
|
||||
### 3.4 Site
|
||||
|
||||
**What it does today.** Thirteen static pages plus 404 (`site/index.html`, `litepaper.html`, `live.html`, `bench.html` 611 KB rendered from `docs/bench-log.md`, `evidence.html`, `ledger.html` 312 KB generated, `miner.html`, `miners.html` from `miner-bench.json`, `wallet.html`, `metamask.html`, `faucet.html`, `explorer.html`, `block.html`, `address.html`). `site/build.mjs:49-50, 357-372` injects `partials/head.html` (self-hosted fonts, tokens), `nav.html`, `footer.html`, stamps download links from `downloads.json`, inlines `journey.json`. `scrub.mjs` runs on bench and miners only (`build.mjs:171, 401`). Live data: index polls `/api/live` every 2 s (`index.html:704`; 10 s while off); the light client fetches `/api/checkpoint` and imports two noble libraries from cdn.jsdelivr (`verify/verify.js:5-6`); `live.html` polls every 2 s (`:665-666`); faucet POSTs `/api/faucet`. APIs (`site/api/`): `live`, `stats`, `supply`, `explorer`, `checkpoint`, `faucet`, `log`, all reading Neon tables written by `tools/observer`. Edge cache: live 1 s, stats and supply 10 s, explorer 5 s (`vercel.json`). The live HTML (one curl, 20:02Z) is 60,009 bytes, served with HSTS (preload), `X-Frame-Options: DENY`, nosniff, `Referrer-Policy: strict-origin-when-cross-origin`, no CSP; download buttons stamped "v0.3.14 · 45.4 MB"; "Public testnet: not yet open; the devnet build is here for people who want to look."
|
||||
|
||||
**Comparators and the exact gap** (approximate unless a page is named).
|
||||
|
||||
| Comparator | Their page or element | Ours |
|
||||
|---|---|---|
|
||||
| kaspa.org, ethereum.org | A Developers or Docs nav item | Nav is Litepaper, Live devnet, Engineering log, Miner, Wallet, Evidence, GitHub (`partials/nav.html:14-21`); builders land on a litepaper section (`index.html:335`) |
|
||||
| getmonero.org/downloads | Every platform, hashes and signing keys on one page | Split across `index.html:363-365`, `/miner#get`, `/wallet`; the homepage's Linux and HiveOS button links `/miner#get`, not a file; sha256 shown only for the HiveOS package (`miner.html:558`), none beside the Windows and Mac buttons, no signing key, no verify line |
|
||||
| z.cash, aztec.network | Named people, a transparency page | "one founder, pseudonymous" once in the litepaper; the Reddit review's key-powers table (round 4, section 3 row 8) absent |
|
||||
| kaspa.org, getmonero.org | Media kit, press kit, language switch, newsletter | None (`site/` has no `/press` or `/brand`) |
|
||||
| ethereum.org, kaspa.org | Light and dark | Dark only; only the litepaper honours `prefers-color-scheme` |
|
||||
| succinct.xyz, aztec.network | 1200x630 share cards | Home, litepaper, live, explorer use 256 px `og-small.png` with `summary` (`index.html:15-19`); miner, wallet, bench, evidence use the large card |
|
||||
|
||||
**Lighthouse, estimated from source (approximate; no run).** Common: `lang="en"`, a skip link (`partials/nav.html:1`), one `:focus-visible` ring (`head.html:22`), self-hosted fonts with `font-display:swap`, two preloads and fallback metrics (`head.html:1-19`), no render-blocking third-party CSS, canonical, description, OG and twitter tags on every page.
|
||||
|
||||
| Page | Performance | Accessibility | Best practices | SEO | What holds it down |
|
||||
|---|---|---|---|---|---|
|
||||
| index.html | 78 to 85 | 88 | 95 | 92 | 89 KB HTML, 35.0 KB inline JS, 24.0 KB CSS; two rAF canvas loops at display rate with no visibility or intersection stop (`:790, 853, 857`); 2,880 px webp with no srcset; h3 before the first h2 (`:344, 359`); ember on bone 3.08:1 at 15 px in the light section (`:119-120`), eyebrow `#7A776F` on bone 3.97:1 at 12 px (`:130`); 256 px OG card |
|
||||
| explorer.html | 88 to 93 | 90 | 100 | 85 | 32 KB, no images, no third party; `aria-live="polite"` on an 8-tile strip refreshed every 10 s (`:222`); not in the sitemap |
|
||||
| live.html | 85 to 90 | 88 | 100 | 85 | 64 KB, 35.6 KB JS; the canvas loop is capped at 30 fps and stops off screen (`:605-612`), good; `aria-live="polite"` on the 8-cell strip every 2 s (`:225`); canvas `aria-hidden` with no text alternative (`:241`) |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q5 | "final" during a pause; 90-word hero; 24 GB contradiction; thumbnail OG (section 1) | `index.html:345, 494, 810, 838, 15-19` | the project lead-visible | 3 | site owner | section 1 |
|
||||
| Q50 | No downloads page: hashes only for HiveOS, no signing key, no verify line, the Linux button goes to an anchor | `index.html:363-365, 516`, `miner.html:549-558` | user-visible | 2 | site owner | `/download` lists every artefact with version, size, sha256, the OTA key fingerprint and a verify command per OS; the Linux button links the file |
|
||||
| Q51 | `/miners` has 6 rows, two card models, "not measured" MH/W on every row; the eleven-card fleet table is not ingested | `miners.html:183`, `miner-bench.json` | user-visible | 2 | miner-community-lead | rows carry W and MH/W for every measured card |
|
||||
| Q52 | `dl\.igneum` in `forbidden-strings.txt` matches the public download host on three built pages; the scrub guards only bench and miners | `forbidden-strings.txt:25`, `build.mjs:171, 401` | operator-visible | 0.5 | site owner | the pattern becomes `dl\.igneum\.network/dl/(?!public/)` or `/dl/[0-9a-f]{12,}`, and the check runs on every built page |
|
||||
| Q53 | Sitemap misses `/miner`, `/explorer`, `/faucet`, `/ledger`; every lastmod is 4 or 5 Oct; 404 says "These four pages are everything the site has" | `sitemap.xml`, `404.html:138` | cosmetic | 0.5 | site owner | the sitemap is generated by the build from the page list |
|
||||
| Q54 | `aria-live="polite"` on strips refreshed every 2 s and 10 s (a screen reader hears eight values every refresh) | `live.html:225`, `explorer.html:222` | user-visible | 0.5 | site owner | `aria-live="off"` on the strips plus one polite status line on state change |
|
||||
| Q55 | Homepage canvases never stop off screen or cap their rate | `index.html:790, 853, 857` | user-visible | 1 | site owner | 30 fps cap and IntersectionObserver as `live.html:605-612` |
|
||||
| Q56 | Light-section contrast fails (3.08:1 links, 3.97:1 eyebrow) | `index.html:119-120, 130` | cosmetic | 0.5 | site owner | AA on every token pair; the same node script as Q12 |
|
||||
| Q57 | "devnet v0" eyebrow on the live page; the chain is v4 | `live.html:219` | cosmetic | 0.1 | site owner | matches the app's network word |
|
||||
| Q58 | Heading order (h3 before the first h2) | `index.html:359` | cosmetic | 0.2 | site owner | h2 or a styled div |
|
||||
| Q59 | No team, custody or key-powers page (Reddit round 4 artefact 8); "0 admin keys in consensus" tile unchanged | `index.html:611`; no file | user-visible | 1 (plus the project lead's policy call) | site owner | the key-powers table published; the tile links it |
|
||||
| Q60 | Roadmap dates versus the testnet: the journey says "Public testnet, Aug to Oct 2027" and the litepaper "Pools and the public testnet are August 2027", while `docs/plans/testnet-go.md` has `igneum-testnet-1` seeds up and a go checklist dated 5 October 2026 | `index.html:679`, `litepaper.html:719`, `docs/plans/testnet-go.md:1-4` | the project lead-visible | 0.5 (after the project lead decides which is true) | site owner | one date for the public testnet on the journey, the litepaper and the download notice |
|
||||
| Q61 | No Content-Security-Policy header on the site (the relay has none either); inline scripts throughout, two pinned CDN modules on the homepage | `site/vercel.json:7`, `relay/vercel.json:31-43`, `verify/verify.js:5-6` | operator-visible (security) | 2 | site owner | a CSP with hashes or nonces for the inline scripts and `script-src` limited to self and cdn.jsdelivr; every page renders with no console violation |
|
||||
| Q62 | Copy-law borderline headlines: "Mined by GPUs. Proven by fire." (the brand line, the project lead's call), "GPUs are back · for good", "Last hour's chip is already obsolete.", "Your coins. Final means final.", "Dates slip. Gates do not.", "Install. Start. The card mines and proves.", "Nothing is mined here." | `index.html:343, 344, 424`, `wallet.html` h1, `litepaper.html:678`, `miner.html` h1, `404.html` h1 | cosmetic | 1 | site owner (the project lead rules on the brand line) | each either kept by the project lead's word or reworded |
|
||||
|
||||
**Reddit round 4, still open in the HTML.** Finding 6 (24 GB): half fixed (Q5). Finding 7 (eleven-card table): open (Q51). Finding 8 (admin keys): sentence added to the litepaper (`:659`), tile unchanged, no table (Q59). Finding 15 ("Monero's idea, finished for GPUs"): open (`index.html:444`, `litepaper.html:429`). Finding 16 ("devnet v0"): open (Q57). Finding 22: `/ledger` present, no nav item. Findings 2, 3, 5, 9, 10, 12, 13, 17, 19: fixed in the current files. "0% anyone else in the protocol" legend still live (`index.html:604`).
|
||||
|
||||
**Error states, exact strings.** API unreachable: `/live` after three failed polls shows "OFFLINE", "api unreachable", the canvas frozen with "api unreachable, scene frozen" (`live.html:665, 600`); `/explorer` writes the fetch error into the table on first load and is silent after (`:342-345`); `/block` "The API did not answer: <message>" (`:328`); the homepage "simulated preview · live feed unavailable" and "Live feed unavailable. This is a simulation and its counters count simulated blocks." (`index.html:870-872`). Observer stale over 30 s: "OFFLINE", "observer updated N min ago", "observer offline, last update N min ago" (`live.html:663`); explorer eyebrow "observer offline, last known" (`:318`); homepage "simulated preview · observer offline, last update N s ago" (`:708`). Finality paused: section 2.
|
||||
|
||||
**Already better.** Self-hosted latin subsets with preloads and computed fallback metrics (`head.html:1-19`); ethereum.org and kaspa.org load heavier bundles (approximate). Zero third-party scripts except the two pinned noble modules; no analytics, no cookie banner. Honest live states: "simulated preview · observer offline", "finality paused: N% of weight silent", "api unreachable, scene frozen"; Etherscan shows nothing when its indexer lags (approximate). A browser-side BLS certificate verifier on the homepage (`verify/core.js`), which no comparator homepage has. A public API with a CI field contract (`api/stats.mjs:10-14`) and supply derived from the emission rule with an hourly coinbase check. HSTS with preload, frame DENY, nosniff, immutable font caching, skip link, reduced-motion rule on every page. A dated journey with pass or fail gates inlined at build, and a ledger page of every criticism; none of the six site comparators publish the equivalent.
|
||||
|
||||
### 3.5 Explorer
|
||||
|
||||
**What it does today.** `explorer.html` polls `/api/explorer?blocks=50` every 5 s and `/api/stats` plus `/api/supply` every 10 s (`:342-343`); `block.html` fetches `?block=` or `?height=` once (`:276`); `address.html` fetches `?address=` once; `/block/:id` and `/address/:addr` are Vercel rewrites. Search classification is client-side (`lib/explorer.mjs:19-27`: hash, 0x address, bech32, height) with a server fallback for tx hashes (`api/explorer.mjs:49-59`). The data window is 24 hours of observer rows; balance needs `EXPLORER_EVM_RPC`, unset on the devnet deployment (`docs/plans/explorer.md` section 7).
|
||||
|
||||
**Comparators and the exact gap** (approximate).
|
||||
|
||||
| Comparator | Their feature | Ours |
|
||||
|---|---|---|
|
||||
| Etherscan, Blockscout | A transaction page: decoded input, logs, status, fee | None; a tx hash lands on `/block/<hash>#tx-<hash>` (`api/explorer.mjs:57`); `block.html:310` lists hash, from, to, value, bytes, with from and to "needs an EVM RPC" when unset |
|
||||
| Etherscan, Blockscout | Address page with transactions, token transfers, nonce, contract tab | Blocks mined, 24-hour earnings, balance only with an RPC (`address.html:209, 281`) |
|
||||
| Blockscout | Hosted API reference (Swagger), rate-limit statement | A card on `/explorer` (`explorer.html:232-240`) and `docs/api/public-stats.md` in the repo |
|
||||
| explorer.kaspa.org | DAG view inside the explorer; merge-set order per block | DAG view lives on `/live`; block page has blue work, parents, children, mergeset blues and reds (`block.html:285-325`), no prev and next navigation |
|
||||
| mempool.space | Mempool as the product, fee charts | Mempool count is one tile on `/live` (`live.html:233`); no charts on the explorer |
|
||||
| All four | Per-block status (final, confirmations) | None for an ordinary block (Q10) |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q10 | No finality state; failures swallowed (named under the ten in section 1) | `api/explorer.mjs:16-25`, `explorer.html:222, 309, 342-345` | user-visible | 3 | site owner | during a forced pause every block row carries "paused" and the strip tile reads the Q1 reason; cut the API: the table dims with "api unreachable, last update N s ago" within 10 s |
|
||||
| Q63 | No tx page, no hosted API reference | `api/explorer.mjs:57`, `explorer.html:232-240` | user-visible | 4 | site owner | `/tx/<hash>` with status, fee, from, to, input bytes, shard and proof state; `/api` page with every field and the cache times |
|
||||
| Q64 | Address page: no transaction list, no finality (Q37 covers the finality half) | `address.html` | user-visible | 3 | site owner | the last 50 transfers in and out, each with its block and state word |
|
||||
| Q65 | No prev and next block navigation; no block-by-height neighbours | `block.html` | cosmetic | 1 | site owner | two links from `selected_parent` and the first child |
|
||||
| Q66 | Blocks per second inverted against the other pages ("1.2 s per block" vs "0.9 blocks / s") and difficulty "126.81 M" with a space against "126.81M" on `/live` | `explorer.html:330-331`, `live.html:329` | cosmetic | 0.5 | site owner | section 4.3's one formatter |
|
||||
|
||||
**Already better.** Block page depth for a DAG: parents, children, mergeset blues and reds, selected parent, shards with prover and lag, checkpoint weight fractions and certificates (`block.html:285-325`); explorer.kaspa.org shows less of the mergeset (approximate). Client-side search that accepts a height, a hash, an 0x or bech32 address with no round trip (`lib/explorer.mjs:19-27`). Five-second edge cache with a stale flag from the observer, so a stale page says so on first load.
|
||||
|
||||
### 3.6 Pool
|
||||
|
||||
**What it does today.** `igneum-pool`, a Rust crate (`pool/Cargo.toml`, 12 source files) speaking the spec 09 protocol in its v0 form: newline JSON over plain TCP (`pool/src/server.rs:1-2`, `protocol.rs:1-9`), the project's own message set (`hello`, `welcome`, `authorize`, `seeds`, `template`, `job`, `share`, `share_result`, `solution`, `stats`; `protocol.rs:21-170`). Every share is CPU-verified on the node's warp verifier (`verify.rs:52-64`; 1.35 ms isolated, 2.1 ms under load, `docs/plans/pool.md` section 5). PPLNS only, window in blocks of weight, default 2 (`pplns.rs:1-5`, `config.rs:67`); fee 1 percent, minimum payout 1 IGN (`config.rs:65-66`); EIP-1559 transfers from the pool's coinbase key, at most 16 per round, failed receipts re-credited (`payout.rs:197-299`). One page, `pool/web/index.html`, baked into the binary (`api.rs:166-174`): a stat strip, connect card, address lookup, blocks, payments, API card. Stats API `/api/stats`, `/api/blocks`, `/api/payments`, `/api/miners/<addr>`, `/api/pool-stats`, `/health` (`api.rs:185-200`) with fixture-pinned field contracts (`api.rs:255-272`). Operator surface: a `STATUS` line every 30 s on stdout (`main.rs:152-174`). Deployment: a written, unexecuted systemd unit behind Caddy (`pool/README.md:75-116`; "not executed", `docs/plans/pool.md` section 8). Nothing is deployed. The horizon tree equals `igneum-wt-pool-rebase` HEAD 269364a.
|
||||
|
||||
**Comparators and the exact gap.**
|
||||
|
||||
| Comparator | Their feature | Ours |
|
||||
|---|---|---|
|
||||
| 2miners | Per-worker hash rate charts over time; payout history per miner; worker-offline alerts by email or Telegram; estimated earnings per day; luck | A single 10-minute and 1-hour number, samples kept 15 minutes and not persisted (`state.rs:704-705, 779-781`); the API returns 50 payments and the page renders none of them (`api.rs:148`, `web/index.html:181-194`); no contact field (`protocol.rs:50-60`); no earnings estimate; luck defined inverted against MiningPoolStats' convention (`api.rs:72-74`) |
|
||||
| WoolyPooly | Solo mode; worker offline alerts | `scheme` hard-coded `"pplns"` (`server.rs:53`, `api.rs:80`); no alerts |
|
||||
| Kaspa acc-pool (approximate) | Prometheus and Grafana | No `/metrics`; stdout only |
|
||||
| Stratum v2 reference (github.com/stratum-mining/stratum, not cloned, approximate) | Noise-encrypted transport, job negotiation, binary framing, header-only mining | Plain TCP while spec 9.3 demands TLS 1.3 and `binding` is sent empty (`server.rs:1-2`, `protocol.rs:58-60`, `09-pool-protocol.md:37, 43, 49`); mode A only; JSON lines, and the `template` ships the whole `RpcRawBlock` to every member every second (`node.rs:147-164`) |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q67 | Member transport is plaintext TCP against spec 9.3's TLS 1.3 with session binding; `binding` ignored | `server.rs:1-2, 115`, `protocol.rs:58-60` | user-visible (hijack risk) | 8 | consensus-engineer | rustls listener; a replayed `authorize` on a second connection is refused (O-9.7) |
|
||||
| Q68 | Node down shows "difficulty 0, DAA 0" and a ramp-day-0 reward; no "node unreachable" state | `node.rs:350-368`, `web/index.html:163-164` | the project lead-visible (once deployed) | 2 | miner-community-lead | the page renders "Node unreachable since <time>" against a stopped node |
|
||||
| Q69 | Raw integers (DAA, block count, shares) with no separators; difficulty uses `toLocaleString()` with no locale, so it varies by browser; hash rate 2 decimals against the site's 1 | `web/index.html:152, 163, 172, 190` | the project lead-visible | 1 | miner-community-lead | every number formatted as section 4.3 says |
|
||||
| Q70 | Ledger is a JSON file rewritten every 15 s; hash-rate samples and check cost not persisted, so a restart zeroes every rate tile and the 24-hour luck | `state.rs:704-710`, `main.rs:131-144` | user-visible | 4 | miner-community-lead | restart test: tiles recover within one sample window; README names the loss window |
|
||||
| Q71 | Address lookup never shows the payment history the API returns | `api.rs:148`, `web/index.html:181-194` | user-visible | 1 | miner-community-lead | payments table under the lookup |
|
||||
| Q72 | No per-miner history, no charts, no worker-offline notice, no `/metrics`; `/health` always ok | `state.rs:779-781`, `web/index.html:187`, `main.rs:152-174`, `api.rs:195` | user-visible, operator-visible | 4 + 4 + 2 + 1 | miner-community-lead | hourly buckets per address kept 7 days with a sparkline; opt-in webhook after 10 min silence; Prometheus text endpoint; health reflects `net.synced` and template age |
|
||||
| Q73 | Finality: the pool confirms and pays on blueness and has no finality concept; the page says nothing during a pause (a design choice to state) | `node.rs:279-345`, `server.rs:9` | user-visible | 1 | miner-community-lead | the page carries the network's finality state and the sentence "payouts follow blue confirmation, not finality" |
|
||||
|
||||
**Already better.** Every share CPU-verified at the node's own engine, no sampling (`verify.rs:52`; spec 9.8 item 5); most pools trust the miner's claim for low-difficulty shares (approximate). The member's vote key rides in every header it hashes (`node.rs:47`), so pool concentration does not become vote concentration; no Stratum pool does this. PPLNS split snapshotted at find time, credited only on blue confirmation, orphans pay nobody (`state.rs:821-852`). Failed payout receipts re-credit the balance (`payout.rs:288-292`). Exact binary share weights (`vardiff.rs:138-140`). Field contracts pinned by fixture tests.
|
||||
|
||||
### 3.7 Discord
|
||||
|
||||
**What it does today.** `tools/community/discord-hooks.mjs` (798 lines, Node 22, no dependencies). Webhook URLs in `~/.config/igneum/discord` (`:18-19, 41, 520`); state in `~/.config/igneum/discord-hooks-state.json`. Posts: network pulse to #numbers at 03:00, 09:00, 15:00, 21:00 UK (`shapePulse` `:236-273`); daily digest 09:00 (`:276-299`); weekly numbers Monday 09:00 (`:302-352`); release to #announcements by the shipper (`:394-409`); incident open and resolve to #incidents by hand or by the watcher (`:412-433`). Watcher conditions: `finality_paused` (5-minute hold), `proof_lag` (900 s), `observer_silent` (180 s) (`WATCH` `:37`, `:437-472`); one incident per condition, resolved after 120 s clear (`:490-503`). Idempotency keys per post (`:562, 569`); 429 backoff (`:534-551`); a forbidden-string guard before every post (`:47-96`); `allowed_mentions: {parse: []}`. Runner: a systemd timer on igneum-build-1, `tick --live` every minute (`infra/build-server/discord-hooks/`). Per `docs/community/discord-hooks.md:81-82`, no live post has been made and the install has not run. Tests: 31 pass; every live poster is stubbed.
|
||||
|
||||
**Comparators and the exact gap** (approximate).
|
||||
|
||||
| Comparator | Their feature | Ours |
|
||||
|---|---|---|
|
||||
| Ethereum, Kaspa, Monero Discords | Ticker bots renaming channels with height, hash rate, price | None; a webhook cannot rename a channel, a bot token is needed |
|
||||
| The same | Embed colour by severity; a pinned current-status message; "resolved" as an edit of the open message | Single ember colour for every kind (`:33, 158`); no pin; resolve posts a second message (`:706-712`) |
|
||||
| The same | Explorer links per block or lock | Only `/live` and `/miner` links (`:271, 297, 350, 420, 432`) |
|
||||
| The same | Reorg or stall alerts | No condition for a block-production stall (the fixture shows 11 zero minutes at 18:30 to 18:41Z) or a reorg (`:437-472`) |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q7 | Bot not live; pre-existing never opens; pulse hides the pause (section 1) | `discord-hooks.mjs:481-485, 252-255, 158` | the project lead-visible | 3 | miner-community-lead | section 1 |
|
||||
| Q74 | No block-production stall condition, the actual first signal of tonight's 18:30Z gap | `:437-472`, `fixtures/discord/live.json` | user-visible | 2 | miner-community-lead | `blocks_stalled` condition, 3 minutes, fixture test |
|
||||
| Q75 | Resolve posts a new message instead of editing the open one | `:706-712` | cosmetic | 2 | miner-community-lead | PATCH `/messages/<id>` with the resolve fields |
|
||||
| Q76 | Embeds link only `/live` and `/miner`; the last lock could link `/block/<hash>` | `:271, 297, 350` | cosmetic | 1 | miner-community-lead | last lock links its block page |
|
||||
| Q77 | "Last lock" names 93 voters while the hash-rate field says 56 vote keys active; readers will ask | `:259, 262` | user-visible | 1 | miner-community-lead | one clause explains the two counts, or one number |
|
||||
| Q78 | Four semicolon-balanced two-beat sentences in the shipped incident texts ("Blocks keep being produced; the chain is not locking checkpoints", "finality pauses rather than locks on a minority", "Vote keys are not machines; the machine count is owed", "Blocks are produced and final as usual; their proofs arrive late") | `:347, 448, 452, 458` | cosmetic | 0.5 | miner-community-lead | reworded as single statements |
|
||||
|
||||
**Already better.** Release embeds carry the full sha256 of every artefact plus the exact verify commands (`:399-406`); most project bots post a link. Every post passes a guard that refuses the founder's name, hosts, IPs, paths, webhook URLs and mentions (`:47-96`). Every number names its API field and the posts link the raw JSON (`:314-341`). Incident texts state who is affected per tier (`:449, 458, 467`). A dry-run HTML preview renders the exact card before anything goes live (`:602-634`).
|
||||
|
||||
### 3.8 Fleet tooling
|
||||
|
||||
**What it does today.** On the `gpu-fleet` branch (`/Users/joshm/Projects/igneum-wt-gpu-fleet/tools/fleet/`, read-only; the `horizon` tree holds only `publish-fleet.sh`): providers `vast.py` (console.vast.ai v0: filtered bundle search `:47-54`, rent with optional onstart `:61-72`, instances, destroy, a price-stamped `ledger.jsonl` `:88-102`) and `runpod.py` (`:27-44`); the orchestrator `fleet.py` (rent-card, wait, setup, status, run, pull, destroy, tail, sh, dn2-version, list; `:5-13, 183-187`); the shared library the 6 October rule demands, `lib/box.py` (`Box.run/put/alive`, `install_payload` sha-checked, `stop_node/start_node`, `height/daa/peers/synced/wait_synced`, `exec_status/exec_tip/proving_status/paid_segments/paid_shards/state_root`, `node_version`, `rejects_since`, `max_reorg_since`, `start_miner/stop_miners/start_prover/stop_all`, `destroy`; `Registry`) and `lib/standing.py` (roster, install, check, update, rerent, loop, `weight_check`); `lib/test_box.py` runs against dn2-3 with one known-failed case (`:10-12`) and a PASS line (`:37`), by hand, not in CI. Box side: 20 `box-*.sh` scripts (setup, matrix, ember, prover, dn2, standing supervisor, node-swap, exec-snapshot, wave, wave-pool, pool, rig, rig-prover, rehearsal, kill, datadir, floor-v5, segal-host) plus `dn2-kill.sh`, `dn400-worker.sh`, `rehearsal-kill.sh`. Collectors: `collect.py`, `bps-collect.py`, `night.py` (60 s probe, 15-minute rows, exec-reset re-run `:45-55`), `autorun.py`, `restage.py`, `swap.py`. Gates: `devnet2-gate.sh` (PASS = zero rejects, max reorg under 4, version equal, exec roots equal at a common height, one paid segment; `:92-100`), `canary.sh`, `canary-next.sh`. Page: `page.py` builds `fleet.json`; `publish-fleet.sh` copies it and runs a full Vercel production deploy (`:10`); `publish-0315.py` is the first standing publish. PC job runner `tools/build-job.mjs` (run, publish, watch, fetch, verify; targets ae432dc7 and 1ccfe586; signed kinds `run`, `fetch`, `collect`, `restart`, `update-now`, `shard-benchmark`, `build`; `--stop-miners` becomes `stop_miners_first`, consumed at `app/igneum-app/src/jobrun.rs:563`). Console `tools/console.mjs` (post, log, machines, chain, jobs, builds, tuning, results, sync, url). CI checks in `tools/ci` (22): bash-body, check-workflow-shell, commit-string, copied-sources, identity-check, install-hooks, kit-path, link-check, no-conflict-markers, no-foreign-tree-writes, no-secrets, override-json, pinned-guests, playbook-quit, prover-socket, ps-drive-ref, public-api-check, second-engine, signer-pipe, windows-spawn, plus fixtures; `pgrep-self-match-check.sh` exists on `gpu-fleet` only (its `ci.yml:82-83`), not on `horizon` or `master`.
|
||||
|
||||
**Comparators and the exact gap** (approximate unless cited).
|
||||
|
||||
| Comparator | Their feature | Ours |
|
||||
|---|---|---|
|
||||
| Vast CLI | `search offers` with any filter expression; `create instance --onstart`; `logs` without ssh | A fixed filter set (`vast.py:47-52`); `rent-card` never passes onstart (`fleet.py:79`), so install is a second ssh step (`:102-109`); `tail` needs ssh (`:180`) |
|
||||
| SkyPilot | Declarative task YAML, `autostop -i`, spot recovery, `sky status`, `sky logs` | Imperative; one-shot boxes have no autostop (the caps at `page.py:45` are display only; 59 boxes running at 20:02Z); recovery is `standing.loop` after two dead checks at 600 s (`lib/standing.py:126-140`); `check` prints raw dicts (`:148`) |
|
||||
| Nomad | Restart policy: attempts, interval, delay, mode; allocation health in the UI | `box-standing.sh` restarts every 60 s with `pkill -9`, no cap, no backoff, no failure count (`:33, 46-49, 70`) |
|
||||
| HiveOS farm view | Per-rig cards: hash, temps, fan, accepted and rejected, last seen, flight sheets, bulk actions | The fleet page card shows card, state pill, "doing", USD/h, hours only (dlsite `index.html:176`); `standing.py:56` strips the `miner=` field the supervisor writes (`box-standing.sh:64` has MH/s, accepted and rejected, GPU util, watts); `console.mjs:114-116` has the HiveOS-style row for the app machines, not for rented boxes |
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q86 | The fleet page is blind to the standing fleet, to death and to finality. `page.py:43-47` publishes `standing` and `devnet2`, and the page's `index.html` references neither (grep 0; it reads boxes, results, phases, night, log, `:243`); a dead box reads "running" for up to 20 minutes because state comes from `boxes.json` (`page.py:38`) and `standing.jsonl` is never read; no fleet or console script reads `finality_active` (only `bps-collect.py:30` tails "Finality:" log lines), so tonight's pause was found by hand (`CLAUDE.md`, the standing-fleet rule) | `page.py:38-47`, dlsite `index.html:243`, `lib/standing.py`, `tools/console.mjs:124` | the project lead-visible | 6 (standing block, USD per day, Devnet 2 height and last gate line 2; "unreachable since HH:MM" from `standing.jsonl` within 2 minutes 2; `getFinalityCheckpoints` in the standing check and "finality paused since, reason" on the page and the console 2) | fleet agent | the page shows the standing count and spend; a box killed by hand reads "unreachable since" within 2 minutes; a forced Devnet 2 pause reads on the page and in `console.mjs chain` |
|
||||
| Q87 | `rerent` does not set up or supervise the new box: the docstring promises both (`lib/standing.py:14-16`), the code rents and patches rows (`:78-91`); a re-rented card bills idle | `lib/standing.py:78-91` | the project lead-visible (spend) | 2 | fleet agent | the new label shows synced and mining on the page within the install time |
|
||||
| Q88 | One-shot boxes have no autostop; `cap_vast 1000` and `cap_runpod 500` enforce nothing (`page.py:45`; `vast.py:61-72` has no cap check) | `page.py:45`, `vast.py:61-72`, `autorun.py` | the project lead-visible (spend) | 1.5 | fleet agent | `autorun` destroys a one-shot box N minutes after its done line; `rent` refuses above the cap |
|
||||
| Q89 | Supervisor restart loop without backoff or a failure line | `box-standing.sh:46-49` | operator-visible | 1.5 | fleet agent | after N failures a `standing_node_failed` line and a page flag |
|
||||
| Q90 | Dropped miner and GPU facts: `standing.py:56` strips `miner=`; `gpu=` never parsed | `lib/standing.py:56`, `box-standing.sh:64` | operator-visible | 1 | fleet agent | MH/s, accepted and rejected, watts per box on the page (the HiveOS card) |
|
||||
| Q91 | A transient ssh drop counts as death: `Box.run` returns 124 on timeout (`lib/box.py:49`), `check_one` marks dead on any rc (`standing.py:54`), the loop never asks the provider (`fleet.py:48-60` `refresh_ssh` unused); Vast proxy drops are known (`canary.sh:41`) | `lib/box.py:49`, `lib/standing.py:54` | operator-visible | 1 | fleet agent | provider status consulted before a dead check counts |
|
||||
| Q92 | `lib/test_box.py` pins a stale binary and digest (`:31` igneumd-0313, `:34` digest 4a0b8726; 0.3.15 is 06211d55, `publish-0315.py:15`) and is not scheduled | `lib/test_box.py:31-34` | operator-visible | 1.5 | fleet agent | a nightly PASS line on the page |
|
||||
| Q93 | The Devnet 2 gate reads no peers, no finality, no exec `blocked` (`read_box`, `devnet2-gate.sh:51-53`; `night.py:15` reads `blocked`, the gate does not) | `devnet2-gate.sh:51-53` | operator-visible | 2 | fleet agent | FAIL on 0 peers or finality paused at step 4 |
|
||||
| Q94 | `publish-0315.py` hard-codes one worktree and release (`:14-18`); `page.py:13` points at the master checkout's publish script; every publish is a full Vercel deploy | `publish-0315.py:14-18`, `page.py:13`, `publish-fleet.sh:10` | cosmetic | 2 | fleet agent | `publish.py` reads paths and sha from the release manifest; the page deploys as one file |
|
||||
| Q95 | Five 6 October rule rows have no `tools/ci` check and are therefore OPEN by the project's own rule (table below) | `CLAUDE.md` 6 October rules, `tools/ci/` | operator-visible | 5 (1 each) | fleet agent (consensus-engineer for the gate check) | each check fires on a known-failed fixture and passes a known-good one |
|
||||
|
||||
**The 6 October rules against their checks.**
|
||||
|
||||
| Rule (CLAUDE.md, 6 October 2026) | Check today | What the check is |
|
||||
|---|---|---|
|
||||
| `pgrep -f` with a literal pattern is banned | `pgrep-self-match-check.sh` on `gpu-fleet` only; absent from `horizon` and `master` | merge it; wire it in `ci.yml` on master |
|
||||
| Box operations live in one tested library | none; `devnet2-gate.sh:40-41` and `canary.sh:32-33` define their own `SSH()` and `SCP()` past `lib.box` | fail any file outside `tools/fleet/lib` that runs `ssh -i ~/.ssh/igneum-fleet` |
|
||||
| A watcher verifies the chain-side fact and is trusted only after a known-finished and a known-failed case | none; `devnet2-gate.sh` and `canary.sh` have no `--self-test` | a `--self-test` flag on every gate with both fixtures |
|
||||
| A standing box never leaves the live chain for an experiment; never remove over 10 percent of weight in an hour | `weight_check` exists (`standing.py:108-125`) but nothing enforces its call; `Box.destroy` (`box.py:136`) has no standing guard | destroy refuses standing rows; CI greps that scripts calling `stop_miners` on a label call `weight_check` first |
|
||||
| No live build before the Devnet 2 PASS line | none; `devnet2-status.json last_gate` reads "not run during the block-rate experiment" | the ship script requires `PASS <sha16>` in `devnet2-status.json`, as `commit-string-check` runs inside `build-remote.sh` |
|
||||
| Have checks | commit-string, override-json, ps-drive-ref, no-foreign-tree-writes, playbook-quit, second-engine | |
|
||||
|
||||
**Error states, exact strings.** Console machines (`tools/console.mjs:114`): `STOPPED (<reason>) <ago> ago`, `SILENT`, `live`, then `seen <ago> ago`, `synced` or `not synced`; cards `(stale, not in the total)` and `| <fault>` (`:115`). Console chain (`:124`): `last lock #N (blue N) | lag Ns STALE`; no "paused" word in `console.mjs` (the web tile has it). `fleet.py`: `no ssh` (`:116`), `ssh timeout` (`:118`), `scp failed` (`:106, 133`), `pull failed: ...` (`:145`). `lib/box.py`: `(124, "", "timeout")` (`:49`), `SshError("<label>: rc N: <err>")` (`:53`). `standing.py`: `re-renting <label> -> <new>` (`:134`), `behind: <label> <sha> wanted <sha> (a publish script moves it; the loop only reports)` (`:137`; "behind" is the binary sha, never height), `standing check: N boxes, N alive, N synced, N behind` (`:138`). Fleet page: the state pill (renting, installing, running, done, failed) and `doing` text only; no dead, behind or finality string exists. `devnet2-gate.sh`: `FAIL <box>: N rejected blocks`, `FAIL <box>: a selected-chain reorg of depth N`, `FAIL <box>: version V`, `FAIL <box>: exec root at height H ... differs`, `FAIL no segment record paid on any box during the run` (`:92-100`).
|
||||
|
||||
**Already better.** Every rent and destroy writes a price-stamped ledger line (`vast.py:43-45`, `runpod.py:25-26`), which Vast's CLI does not (approximate). The Devnet 2 gate compares exec state roots across every box at one height (`devnet2-gate.sh:98-99`), beyond any SkyPilot or Nomad health check. Gates read chain facts (height, paid segments, reorg depth), not process names. The supervisor runs the exec-recovery recipe by itself (`box-standing.sh:66-67`). The 10 percent weight rule is a real function (`standing.py:108-125`). The console distinguishes stopped-on-purpose from silent (`parse.mjs:134-140`).
|
||||
|
||||
### 3.9 The node's operator surface
|
||||
|
||||
**What it does today.** Fork-added flags (`igneum-wt-ship0315/vendor/igneum-node-0315/kaspad/src/args.rs`): `--devnet-suffix`, `--evm-rpclisten` (default 26790), `--evm-disable`, `--override-params-file`, `--igneum-exec-snapshot` (`:297`), `--ua-rule` (`:440`), `--rocksdb-wal-dir` (`:502`), `--devnet` alias `--igneum-devnet`. Kept from kaspad: `--loglevel` with per-subsystem `<subsystem>=<level>` (`kaspad/src/args.rs:262-271`), `--perf-metrics` (`:446`, debug lines only), `--utxoindex`, `--rpclisten-borsh`, `--rpclisten-json`, `--externalip`, `--appdir`. Env knobs: `IGNEUM_PROOF_VERIFIER`, `IGNEUM_PROOF_VERIFY`, `IGNEUM_DEVNET_GENESIS_BITS`, `IGNEUMD_DEVNET_BPS`, `IGNEUM_POW_STRIKES` and `IGNEUM_ATTACK_TS_OFFSET_MS` (feature-gated after X19). Finality logs (`consensus/src/processes/finality.rs`): `Finality: checkpoint {i} determined: block {h} (blue score, daa)` (`:625`), `Finality: certificate at index N received: X of Y voters, weight A of B` (`:1127`), `Finality: CONFLICTING certificate at index N` (`:1023`), `Finality: no body tip passes through locked checkpoint ...; fork choice falls back to depth finality` (`:1448`). No pause or resume log line; "paused" exists only as the polled RPC reason (`window filling, N of M`, `active`, `paused`; `igneum-node-0310 finality.rs:1794-1807`); the conflict reason is "O-3.17, not reported yet". RPC `getFinalityCheckpoints` returns `finality_active`, `finality_reason`, `window_filled_daa`, `window_full_daa`, latest lock (`rpc/core/src/model/finality.rs:293-299`); no provisional field, no frozen-table share. Exec JSON-RPC on one listener: `igneum_getExecStatus` (executedTip, blocked, startedFrom), `igneum_getProvingStatus`, `igneum_getFinalityView`, `igneum_submitProofRecord` (`igneum/exec/src/rpc.rs:858-979`). Metrics: no Prometheus endpoint (no hit in the fork's Cargo files). X19 (`docs/fud-ledger.md:1936-1946`): a slow clock disconnecting peers silently, `time_offset` unused, attack env compiled in, a dead override field; fixed on branch `ledger-fixes-0311` fbb0082a (one WARN per minute clock-skew line, attack env behind `attack-switches`, override refuses the field), pending merge; not done: the local-ahead mirror case, the faketime run, the skew field in `getBlockDagInfo` (`docs/plans/node-changes.md:18-19`).
|
||||
|
||||
**Comparators and the exact gap.** kaspad parity holds (`vendor/rusty-kaspa/kaspad/src/args.rs:241` loglevel, `:259` rpclisten-borsh, `:348` utxoindex, `:392` externalip, `:401` perf-metrics) but the fork's own subsystems (finality, exec, proving) got no new knobs. geth (approximate): `--metrics` with Prometheus or InfluxDB, `--log.format json`, `--http.api` allowlist; the fork has none, and writes on the EVM listener share it with reads (`rpc.rs:920`). reth (approximate): `--metrics`, `RUST_LOG` env filter, `reth db stats`, Grafana dashboards in the repo; the fork has no DB tool or dashboards. monerod (approximate): `status`, runtime `set_log`, `limit_rate`, `sync_info`; the fork's log level is start-only, no bandwidth limit, status is `igneum-miner watch` plus `getConnectedPeerInfo`.
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q96 | No log line on a finality flip: an operator tailing the log never sees the pause begin or end | `consensus/src/processes/finality.rs` (no pause line) | operator-visible | 1.5 | consensus-engineer | fast-time simnet prints `Finality: paused (<reason>)` and `Finality: resumed at index N`, one line per flip |
|
||||
| Q97 | `finality_reason` says `paused` and nothing else: no share that left, no frozen-table hold, no expiry, no conflict reason (O-3.17) (the node half of Q1) | `finality.rs:1794-1807` | the project lead-visible (through every surface) | 1.5 (counted in Q1) | consensus-engineer | tonight's pause reads "paused: 42.7 percent of weight left the window; held by the table frozen at lock 6842 until DAA 216,402" |
|
||||
| Q98 | No `/metrics`: finality_active, latest lock, exec tip, blocked, paid segments, peers, DAA as Prometheus gauges (the axum listener exists) | `igneum/exec/src/rpc.rs` | operator-visible | 3 | execution-engineer | `curl :26790/metrics` scraped by a Grafana dashboard committed under `infra/` |
|
||||
| Q99 | Log level is start-only; no runtime change (monerod `set_log`, reth env filter) | `kaspad/src/args.rs:262-271` | operator-visible | 2 | consensus-engineer | an RPC or SIGHUP changes the level without a restart |
|
||||
| Q100 | Clock-skew field in `getBlockDagInfo` and the local-ahead mirror case still owed after X19 | `docs/plans/node-changes.md:18-19` | operator-visible | 2 (after the `ledger-fixes-0311` merge) | consensus-engineer | `faketime -60s` gives one WARN and the field |
|
||||
| Q101 | No `--evm-rpc-api` allowlist: submit and export share the listener with `eth_` reads | `rpc.rs:920` | operator-visible (security) | 2 | execution-engineer | a public listener answers `eth_` and refuses `igneum_submitProofRecord` |
|
||||
| Q102 | No JSON log format: collectors grep four reject patterns (`lib/box.py:119`) | `kaspad` logger | operator-visible | 2 | consensus-engineer | `--log-format json`; the gate reads a field, not a pattern |
|
||||
| Q103 | No `igneumd db` subcommand (column sizes, exec snapshot tip, finality blob layout) | none | operator-visible | 3 | execution-engineer | `igneumd db stats` on a devnet datadir |
|
||||
| Q104 | Merge `ledger-fixes-0311` (X19) into 0.3.16 | `docs/fud-ledger.md` X19 | operator-visible | 1 | consensus-engineer | the faketime WARN on the shipped binary |
|
||||
| Q105 | Eleven fork `.rs` files carry an em dash, one inside a log or help string (upstream origin not verified, approximate) | `igneum-node-0315` (a grep for the character) | cosmetic | 0.5 | consensus-engineer | 0 in any string the operator can see |
|
||||
|
||||
**Already better.** `finality_active` ships with a reason string, which kaspad, geth and reth have no analog for. `--ua-rule` version admission (`args.rs:440-445`) is beyond kaspad. A fixed-height activation is refused by rule; the override file is refused on a dead or duplicate field. Every build binary carries its commit or fails CI.
|
||||
|
||||
### 3.10 Downloads, install and the update flow
|
||||
|
||||
**What it does today.** `site/downloads.json` (updated 2026-10-06T17:48:20Z) lists four artefacts: Windows installer 0.3.14 (45.4 MB), Mac DMG 0.3.14 (41.9 MB), HiveOS package 0.3.14 (24.7 MB), Mac wallet 0.1.4 (19.6 MB), with sha256 and size, under `https://dl.igneum.network/dl/public/` and four stable aliases under `/public/` (`packaging/README-ship.md`, "The public downloads path"). `tools/ship-app.mjs` cuts a version in eleven checked, resumable steps (preflight, bump, inputs, commit, ci, fetch, dmg, copy, manifest, deploy, verify, console; `README-ship.md`). The Windows installer is built only by `windows.yml` on GitHub-hosted runners; since 19:24Z tonight every hosted job dies at start ("recent account payments have failed or your spending limit needs to be increased", `release-0.3.15.md` 6a), so no Windows installer can be cut until billing is fixed. 0.3.15 is staged with `--until manifest`; its Devnet 2 canary failed (block version 1026 on the thirteen-field file) and was rolled back. The ship tool's commit step pushed the release tree to master although the run had `--branch release-0.3.15` (`release-0.3.15.md` 6a).
|
||||
|
||||
**Signing and notarisation state.** macOS: `codesign -s -` (ad hoc) on every binary and the bundle (`build-dmg.sh:118-122`); no Developer ID, no notarisation, no stapling; the engine strips `com.apple.quarantine` from its own bundle on start (`main.rs:78`, `packaging/mac/README.md:51-53`). Windows: unsigned installer and exes; SmartScreen "Windows protected your PC" (`packaging/windows/README.md:76, 111`; `build-installer.ps1:188`); the installer is per-user (`PrivilegesRequired=lowest`) so no UAC for install; the firewall rule asks once, 20 to 50 s in (`ota.rs:981`). Comparators: Signal and Tailscale ship Developer ID-signed, notarised DMGs and Authenticode-signed installers; both open with no interstitial (approximate).
|
||||
|
||||
**The update flow today, precisely** (the baseline for frontier 3.7, reproducible-build attestations with N of M).
|
||||
|
||||
| Step | What happens | Where |
|
||||
|---|---|---|
|
||||
| 1. Build | Node fork and workers built on igneum-build-1 (Linux, Windows cross) and the Mac (macOS); Windows exes reproducible (`-Wl,--no-insert-timestamp`); a binary whose strings lack its commit fails `tools/ci/commit-string-check.sh`. The Windows installer and payload zip are built by `windows.yml` on GitHub-hosted `windows-latest` from `payload-inputs.zip`, whose `payload-inputs.json` (sha256 and size of the zip and every file, fork commit, repo commit) is signed on the Mac with the OTA key and verified in CI against the key compiled into the app and the pin `packaging/windows/node-source.pin` before anything is built (G13, fixed 5 October; 16 local tests pass) | `tools/build-remote.sh`, `cross-remote.sh`, `packaging/windows/push-inputs.sh`, `.github/workflows/windows.yml`, `docs/fud-ledger.md` G13 |
|
||||
| 2. Sign | One Ed25519 key, generated once on the Mac (4 October 2026), seed in `~/.config/igneum/ota-signing-key` (0600), never in the repo or CI; public key the constant `OTA_PUBLIC_KEY_HEX` in `app/igneum-app/src/manifest.rs:28`; `igneum-ota-sign sign` over the canonical manifest bytes (64-byte detached signature, `igneum-app-latest.json.sig`); `publish-manifest.sh` refuses to sign when the embedded key is not the one in `~/.config/igneum`. Rotation = a bridge build with the new constant signed by the old key. The wallet's updater trusts one key with no revocation list (Q31). Key not in hardware (G7 says it will be) | `packaging/ota/README.md` "Keys", `publish-manifest.sh` |
|
||||
| 3. Publish | Manifest (version, per-platform url, sha256, size, kind, `min_supported_version`, notes, `activation_height`) and `.sig` to `dl.igneum.network/dl/<token>/igneum-app-latest.json`, public copy under `dl/public/`; the apps' long-poll on the relay's `/wake` fetches it within seconds; the `igneum-jobs.json` channel uses the same key | `packaging/ota/publish-manifest.sh`, `publish-public.sh`, `relay/api/wake.mjs` |
|
||||
| 4. Check | On start (20 to 50 s in) and hourly with jitter; both files through curl; signature verified over the manifest bytes before parsing; a bad signature reads "manifest signature does not verify" and the manifest is never parsed; a version not newer, or a manifest without this platform, ends the round | `ota.rs:144, 628, 1038-1048`, `manifest.rs:114-120, 175-178` |
|
||||
| 5. Download and verify | Into `<app data>/app/updates/` with resume and `--retry 3`; size and sha256 from the manifest ("sha256 mismatch: the file is not what the manifest signed"); the Mac re-hashes before mounting | `ota.rs:1054-1079, 1124` |
|
||||
| 6. Stage | macOS: mount, copy to `.Igneum Miner.app.new`, run its engine with `--version` and demand the manifest's version; an unwritable folder gives `manual` and "Open the download". Windows: the installer is the staged file | `ota.rs:1096`, `packaging/ota/README.md` "Stage" |
|
||||
| 7. Safe moment | Not urgent: network has not lost over 30 percent of identities in 10 minutes, this machine's minute of the hour, node synced, no hourly boundary within 180 s, no worker starting; a ready update applies anyway after 6 hours. Urgent (fork within 1,800 blocks, unsupported version, Install now) skips every guard. Q4: a passed activation counts as close; no finality input; 0.3.15 adds "never while a remote job is active" | `manifest.rs:322-355` |
|
||||
| 8. Apply | The engine writes `update-pending.json`, starts the helper and exits through its quit path (miners 8 s, node 30 s, last log upload). macOS helper swaps bundles, strips quarantine, reopens, restores `.previous` on failure. Windows helper runs the per-user installer `/VERYSILENT ... /IGNOTA=1` first, with the engine still mining, then the installer stops the engine and relaunches | `packaging/ota/README.md` "Apply", `ota-apply.sh`, `ota-apply.ps1` |
|
||||
| 9. Rollback | The new engine counts starts in `update-pending.json`; a third start without 90 healthy seconds restores the previous version ("rolled back" in Settings); the first Windows update from 0.3.0 has no rollback target | `ota.rs:371` |
|
||||
| 10. What the user sees | "Igneum Miner X is available.", "Downloading X: N%.", "X is ready.", "Installing X. The app restarts itself. Mining continues until then.", "X did not stay up and was rolled back.", "updated to X from Y" in the event feed; Settings: Check now, Install now, automatic switch, the key fingerprint under "Allow remote jobs from Igneum (signed)" | `ui/app.js:29-52`, `index.html` Settings |
|
||||
| 11. Second engine, paused finality | A sweep or `IGNEUM_APP_NO_OTA=1` engine never runs the updater (`engine.rs:601, 778`; the 5 October rule). Nothing in the update path reads finality (Q4) | |
|
||||
|
||||
So the trust today is one key, one builder (the box and the Mac, both the project's), one manifest, no attestation by anyone else, no on-chain record, and the client installs whatever that one key signs at the moment the safe-moment rule allows. That is the baseline frontier 3.7 moves from: N of M attestations in a registry contract, the updater refusing under N, and no install while finality is paused.
|
||||
|
||||
**Rows.**
|
||||
|
||||
| Id | Row | File or screen | Severity | Hours | Owner | Gate |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Q3 | Signing and notarisation; first-run prompts (section 1) | `build-dmg.sh:118-122`, `windows.yml` | the project lead-visible | 6 plus certificates | app owner, the project lead | section 1 |
|
||||
| Q80 | The Windows installer pipeline is dead: every GitHub-hosted job fails at start on billing; the installer and payload zip have no other builder, so 0.3.15 and every hotfix are Mac and HiveOS only until the project lead fixes Billing & plans, or the installer step moves to igneum-build-1 (Inno Setup under Wine, or a self-hosted Windows runner on PC 1) | `release-0.3.15.md` 6a, `.github/workflows/windows.yml`, `packaging/windows/build-installer.ps1` | the project lead-visible | 0 (the project lead: billing) or 4 (installer step on the box or PC 1 as a signed job) | app owner; the project lead | a green `windows.yml` run, or `tools/ship-app.mjs --dry-run` showing the fetch step satisfied from the new builder |
|
||||
| Q81 | The ship tool pushes master whatever `--branch` says (the 19:30Z push of the release tree to master) | `tools/ship-app.mjs` commit step | operator-visible | 1 | app owner | `--branch X` pushes X; a test on a scratch repo |
|
||||
| Q82 | The download page shows no hash for Windows and Mac, no OTA key fingerprint, no verify command; the stable aliases are not explained (Q50 covers the page; this row is the artefact side) | `miner.html:549-558`, `packaging/ota/publish-public.sh` | user-visible | 1 | site owner | `igneum-downloads.json` carries the fingerprint; the page prints it with `shasum -a 256` and `certutil -hashfile` lines |
|
||||
| Q83 | The update key is one software key on one Mac, not in hardware, against G7's "held in hardware and its policy published" | `packaging/ota/README.md` "Keys", `docs/fud-ledger.md` G7 | operator-visible (security) | 3 (a YubiKey or Secure Enclave signer behind `igneum-ota-sign`, the policy page) | cryptographer, app owner | a release signed with the hardware key installs; the key policy is a public page; the software seed is retired |
|
||||
| Q84 | An activation height in the manifest outlives its activation and turns every later update urgent (the Q4 class, publisher side) | `publish-manifest.sh --activation-height` | operator-visible | 0.5 | app owner | the publisher refuses a height at or below the live DAA and drops a stale carried-over one |
|
||||
|
||||
**First 60 seconds, fresh Windows PC** (from the code and the READMEs): download `Igneum-Miner-Setup-0.3.14.exe` from `/miner`, SmartScreen interstitial "Windows protected your PC" (More info, Run anyway), per-user install with no UAC, "Start Igneum Miner now" (`Igneum-Miner.iss:86`), tray "Igneum Miner: starting", Welcome, Cards (detect runs on entry, `app.js:641`), Address, the one-time key sheet, "Start mining"; between 20 and 50 s in, a UAC prompt for the `igneumd.exe` inbound firewall rule arrives on top of whatever screen is up (`ota.rs:981`; declined = the node dials out, never asked again); closing the window gives "Igneum Miner keeps mining" (`host.cpp:354`). Fresh Mac: DMG, drag to Applications, Gatekeeper refuses the ad hoc app (macOS 15 has no Right-click > Open; the user must find Privacy and Security > Open Anyway, approximate), "Starting the engine" (`IgneumMiner.swift:146`), quarantine cleared from the bundle (`main.rs:78`), the same three screens, a menu-bar item with "Pause mining". Both tiers of card see the same screens; an 8 GB card gets two vote identities and a 24 GB card eight (`detect.rs`), which no screen explains (the "a card runs several" line lives on the site, not the app).
|
||||
|
||||
## 4. Cross-cutting
|
||||
|
||||
### 4.1 Accessibility (estimated from HTML and CSS)
|
||||
|
||||
| Surface | Contrast | Keyboard | Screen reader | Motion |
|
||||
|---|---|---|---|---|
|
||||
| Ember dark | bone 17.4:1, ash 7.0:1, ember 5.6:1, molten 11.0:1 on obsidian: passes | `:focus-visible` ring (`app.css:66`), rail arrow keys, Escape on the card and quit (`app.js:681-688, 979`) | `aria-live="polite"` strip (`index.html:50`), `role="dialog" aria-modal` (`:419`), `radiogroup` with `aria-checked` (`:194`), labelled switches and sliders; gaps: the blocks canvas `aria-hidden` with no text alternative (`:241`), the drawer a `div` with no landmark, `user-select:none` on body (`app.css:36`) | reduced motion honoured everywhere (`app.css:67, 113, 142, 148, 157, 420, 437, 466`; `app.js:1137`) |
|
||||
| Ember light | molten `#B8731F` on white 3.81:1 and on bone 3.38:1; ember `#E04A14` on bone 3.61:1: fails AA on the key, the address and every live state (Q12) | same | same | same |
|
||||
| Site | dark passes (ash 6.97:1, ember 5.64:1 on obsidian); the homepage light section fails on links 3.08:1 and the eyebrow 3.97:1 (Q56) | skip link and one focus ring on every page (`nav.html:1`, `head.html:22`); explorer rows clickable with the hash link as the keyboard target | `aria-live="polite"` on strips refreshed every 2 and 10 s (Q54); `aria-live="off"` on the homepage live strip, correct; the DAG canvases `aria-hidden` with no alternative | reduced-motion rule on every page (`head.html:62`); `live.html` caps 30 fps and stops off screen; the homepage does not (Q55) |
|
||||
| Hub | amber `#FFB35C` and red on graphite pass (approximate from the tokens, `relay/ui.html:17, 65`) | buttons and tabs are native elements; no skip link | no live regions; the console is an operator tool | none needed |
|
||||
| Pool page | default light tokens, not audited beyond the formats | native | none | none |
|
||||
|
||||
### 4.2 Copy law
|
||||
|
||||
The em dash character, counted per shipped file: 0 in every `site/*.html` and `site/partials/*.html`, 0 in `app/igneum-app/ui/index.html` and `app.js`, 0 in `relay/index.html` and `relay/ui.html`, 0 in `pool/web/index.html`, 0 in `tools/community/discord-hooks.mjs`, 0 in `app/mac/*.swift` and `app/windows/host.cpp`, 0 in every `app/igneum-app/src/*.rs` user-facing string, 0 in the wallet's `ui/`. The rule holds everywhere that ships.
|
||||
|
||||
Forbidden strings (`site/forbidden-strings.txt`, 25 patterns) per shipped file, with what each hit is:
|
||||
|
||||
| File | Pattern | Count | What it is |
|
||||
|---|---|---|---|
|
||||
| `site/index.html` | `dl\.igneum` | 2 | the public download aliases (`:514-515`); the pattern is meant for the token path (Q52) |
|
||||
| `site/miner.html` | `dl\.igneum` | 5 | the same aliases and the HiveOS installation URL (`:549-557`) |
|
||||
| `site/wallet.html` | `dl\.igneum` | 1 | the wallet alias (`:371`) |
|
||||
| `site/downloads.json` | `dl\.igneum` | 1 | the `base` field |
|
||||
| `relay/ui.html` | `Hetzner` | 1 | the "Hetzner network" card title (`:377`); private console, rule does not apply, but one word |
|
||||
| `app/igneum-app/ui/app.js` | `the project lead` | 1 | a code comment (`:270`); the ui bundle is outside every check (Q22) |
|
||||
| `tools/community/discord-hooks.mjs` | `MacBook`, `\+0100`, `tailscale`, `ts\.net`, `dl\.igneum` | 1, 1, 1, 1, 2 | the guard's own regexes (`:51, 59, 61`), the read-only fleet URL (`:31`), and `ts\.net` matching "stats.net" in a template (`:255`): false positives, none posted |
|
||||
| `pool/README.md` | `Hetzner`, `/opt/igneum`, `CLAUDE\.md` | 5, 2, 1 | the deploy section; not a site page, but it would fail the scrub if copied |
|
||||
|
||||
Every other pattern is 0 on every file. Two-beat antithesis and aphorisms in shipped copy: the headlines listed in Q62, the two Ember lines in Q23, the four Discord incident sentences in Q78.
|
||||
|
||||
### 4.3 Consistency of numbers across surfaces
|
||||
|
||||
| Quantity | Site home | Site live | Explorer | Ember | Hub | Discord | Pool page |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Hash rate | `fmtHash`: 1 decimal, MH/s or GH/s only, "pending" when null (`index.html:719`) | 1 decimal, kH to TH, unit in `<small>` (`live.html:329, 652`) | 1 decimal, kH to PH, integer under 1 kH/s (`lib/explorer.mjs:62-67`) | 1 decimal MH/s (`app.js:1450`) | 2 decimals GH/s, 1 decimal MH/s (`relay/ui.html:246`) | 1 decimal MH/s, 2 decimals GH/s (`discord-hooks.mjs:101-109`) | 2 decimals once scaled (`web/index.html:152`) |
|
||||
| Blocks per second | `toFixed(1)` "blocks / s" (`index.html:375, 722`) | `toFixed(2)` (`live.html:228, 650`) | inverted: "1.2 s per block" (`explorer.html:331`) | not shown | `toFixed(2)` (`relay/ui.html:370`) | 6-hour window | not shown |
|
||||
| Height, counts | `toLocaleString('en-GB')` "chain block" (`index.html:377`) | `compact()` "110.0k blocks" (`live.html:229, 651`) | full digits "Height" (`explorer.html:212, 328`) | `withCommas` | `nf()` separators | `en-GB` separators (`:99`) | raw integers, no separators (`web/index.html:163, 172`) |
|
||||
| Difficulty | not shown | `compact()` "126.81M" | "126.81 M" with a space | `compact()` | `nf()` | 6-hour delta | `toLocaleString()` with no locale |
|
||||
| Miner count | "vote keys active in 10 min (a card runs several)" (`index.html:374`) | "Identities" (`live.html:232`), table heading "Miner" (`:254`) | not shown | "ids" per card | "identities 10 min" | "56 vote keys active" and "93 voters" in one post (Q77) | "Miners / workers" |
|
||||
| IGN | not shown | not shown | reward 4 decimals, supply integer | 4 decimals (`app.js:1364`) | | 2 decimals (`fmtIgn`) | 4 on tiles, 6 in tables |
|
||||
| Price | none anywhere; "pounds a day" once (`index.html:510`); Ember has a `priceNow()` with a devnet zero | | | | | | |
|
||||
| Finality | "#N" or "paused" (`index.html:879`) | "#N, 3 min ago" plus the bar (`live.html:404, 512`) | none | "#N · 1 h ago" (`app.js:1350`) | "active" or "paused" | "checkpoint 6,842 at 55.x% of weight, 1 h ago, 93 voters" | none |
|
||||
|
||||
Row **Q85** (cross-cutting, 2 hours, site owner with the app owner): one shared formatter module (`site/lib/format.mjs`) for hash rate (1 decimal, unit ladder kH to PH), counts (`en-GB` separators, never `compact()` for the same quantity shown in full elsewhere), blocks per second (2 decimals, never inverted), difficulty (one spelling), IGN (4 decimals on tiles, 6 in tables), and one word for a vote identity ("vote key" on every surface); the app, hub, Discord and pool copy the same table as a Rust and a JS constant, with a test that renders the fixture values identically. Gate: the fixture renders byte-identical on all seven surfaces. Severity: the project lead-visible (he reads three of these side by side every evening).
|
||||
|
||||
### 4.4 Error states, one table
|
||||
|
||||
| Condition | Ember | Site home | Site live | Explorer | Hub | Discord | Wallet | Pool |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| Node down | "the node is not running", pill "node failed", "waiting for the node to sync" | "simulated preview · observer offline" after 30 s | "OFFLINE", "observer offline, last update N" | "observer offline, last known" | card red "silent 4h"; "observer STALE N s" | `observer_silent` after 180 s (not live) | "no node", "waiting for a node" | "difficulty 0, DAA 0", no word (Q68) |
|
||||
| API or relay unreachable | "The update failed." (Q11) | "live feed unavailable" | "api unreachable, scene frozen" | error on first load, silent after (Q10) | "offline: Failed to fetch", stale panels (Q6) | n/a | "The API did not answer: ..." (address page) | "stats unavailable" |
|
||||
| Finality paused | nothing (Q2) | "paused" counter, "final" label (Q5) | bar "finality paused: N% silent" (Q1) | nothing (Q10) | tile "paused", no cause (Q6) | nothing (not live), `preexisting` (Q7) | "paused" or "not checkpoint-checkable" | nothing (Q73) |
|
||||
| GPU lost | "Card removed: <name>. Its worker stopped." | | | | card telemetry drops | | | member drops from the count |
|
||||
| Job hung | "job running" notice | | | | "running" forever (Q40) | | | |
|
||||
|
||||
### 4.5 First-run and update flow
|
||||
|
||||
Section 3.10 carries both in full. The two facts to carry forward: a fresh machine on either OS meets two warnings before the first screen (Q3), and the update flow is one software key, one builder, one manifest, with a stale activation height that makes every update urgent (Q4, Q84) and no attestation by anyone outside the project (the frontier 3.7 baseline).
|
||||
|
||||
## 5. Already best in class (honest both ways)
|
||||
|
||||
1. Ember Tune: automatic per-card efficiency tuning with the money consequence on screen, measured 37 to 41 percent more hashes per watt on PC 1 (`ember-tune.md:254, 271`). No miner console ships it.
|
||||
2. The update path: Ed25519-signed manifest verified before parsing, sha256 of the artefact, staged swap, safe-moment rule, machine slots, automatic rollback after two failed starts (`ota.rs`, `manifest.rs`). HiveOS and NiceHash update without a safe-moment rule (approximate).
|
||||
3. The wallet verifies finality certificates itself with the node's consensus code (`finality.rs:101-121`); Touch ID is bound to the exact quote (`engine.rs:539`). No mainstream EVM wallet does either (approximate).
|
||||
4. The site: self-hosted fonts with fallback metrics, zero third-party scripts beyond two pinned modules, no analytics, no banner, HSTS preload, honest live states, a browser-side BLS verifier on the homepage (`verify/core.js`), a dated journey with gates and a public ledger of every criticism.
|
||||
5. The block page: parents, children, mergeset blues and reds, selected parent, shards with prover and lag, checkpoint fractions and certificates (`block.html:285-325`).
|
||||
6. The pool: every share CPU-verified at the node's own engine; the member's vote key in every header it hashes (`node.rs:47`), so pool share never becomes vote share.
|
||||
7. Discord: full sha256 and verify commands in every release post; a forbidden-string guard on every post; incident texts that say who is affected per tier.
|
||||
8. The hub: one phone-first page with every PC's miner, node, app and OTA state fused on one card, stopped and silent as different words, drop-anywhere uploads.
|
||||
9. The relay's wake long-poll: dependency-free, fake-clock tested, 30 a minute per IP.
|
||||
10. The ship pipeline: eleven checked, resumable steps with secrets scrubbed from every line and a signed inputs manifest verified in CI before any build (G13).
|
||||
|
||||
## 6. Open questions and what could not be run
|
||||
|
||||
- The pause's end was not observed: at 20:05:03Z (`live_state.updated_at`) no lock after 6842 had formed. Lane 3 expects the frozen table to expire at DAA 216,402, about 20:40Z. Whoever reads this after 21:00Z should add the resume time and whether any surface showed it.
|
||||
- Which commit the deployed relay runs is unknown from this tree (`relay/README.md` does not name it); Q8 assumes master. The deployed site is master by the GitHub integration.
|
||||
- Lighthouse numbers are estimates from source; no browser run was allowed. The gate for every site row is a real run.
|
||||
- The comparator claims are from memory; where a repository or page is named it is cited, otherwise approximate. The Stratum v2 reference was not cloned.
|
||||
- The fleet and node sections depend on the gpu-fleet worktree and the vendor forks, read tonight; the fleet's own page was not opened.
|
||||
- Hours are agent hours (the project lead's rule); Q3 and Q80 also need the project lead (certificates, GitHub billing).
|
||||
|
||||
## 7. Summary for the coordinator
|
||||
|
||||
Every shipped system was audited against named comparators from the code and the design docs, with 95 ledger rows (ids Q1 to Q105), each with a file or screen, a severity, hours, an owner and a gate; the top ten total 40 agent hours; every row summed is about 230 agent hours. The three findings that matter most: (1) during tonight's two-hour finality pause no public or operator surface could say why, because the node's `finality_reason` is dropped at `tools/observer/observer.mjs:720`, the frozen-table share that held the pause is reported by nothing, Ember has no pause state at all, the homepage drew "final", the explorer showed nothing and Discord was not live; one forced pause on Devnet 2 with a screenshot per surface is the gate (Q1, Q2, Q5, Q6, Q7, Q10). (2) A fresh machine meets two warnings before the first screen (ad hoc Mac signature with a quarantine strip, unsigned Windows installer plus a UAC prompt 20 to 50 s in), and the Windows installer cannot be rebuilt at all tonight because GitHub-hosted runners are blocked on billing (Q3, Q80). (3) The update path treats any passed activation height as "within 1,800 blocks", so every update since 0.3.14's manifest has been urgent, skipping the safe-moment rule (the PC 1 install under a measurement job); the same path has no finality input and is one software key, one builder, one manifest, which is the baseline frontier 3.7 moves from (Q4, Q83, Q84). Rules for main: the observer and every API carry `finality_reason`, `held_by` and `finality_provisional` before the public testnet; the `tools/ci` forbidden-string check covers `app/igneum-app/ui/`; one formatter table for every number on every surface (Q85).
|
||||
94
docs/analysis/income-tiers.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Mining income per tier on igneum-testnet-1
|
||||
|
||||
Generated by `tools/launch/income-tiers.mjs` from `tools/launch/income-tiers.json` (mission item 10, the consequences rule). Do not edit by hand: `node tools/launch/income-tiers.mjs` rewrites it and `--check` fails CI when the two disagree. Every rate and watt figure is a measurement with its source in the first table; nothing is estimated. Testnet coins have no value and the figures below say nothing about any price: the dollar columns are electricity, a cost the reader computes from a measured watt figure and a tariff they choose.
|
||||
|
||||
## The schedule the figures use
|
||||
|
||||
`EmissionSchedule::TESTNET_1` (the testnet genesis, decided 7 October 2026): 100 IGN a block at one block a second, a 90-day ramp from 10 percent, a monthly glide with a two-year half-life (each month pays 2^(-1/24) of the month before), then 1 percent of supply a year from about year 11.4. Of each block, 80 percent goes to the miner who found it and 20 percent to the proving pool. A miner whose key signs its checkpoints keeps the full 80 percent; an unsigned key gives a tenth of it to the proving pool.
|
||||
|
||||
| Moment | IGN a block to the miner | Of the launch figure |
|
||||
|---|---|---|
|
||||
| day 1 | 8.00 | 10.0% |
|
||||
| day 30 | 32.00 | 40.0% |
|
||||
| day 90 (ramp over) | 75.51 | 94.4% |
|
||||
| month 12 | 58.23 | 72.8% |
|
||||
| year 2 | 41.17 | 51.5% |
|
||||
|
||||
A solo card's expected income is its share of network hash times 86,400 blocks a day times the figure above. The tables use day 90, when the ramp is over. Before that, multiply by the ramp row; after that, by the glide row. A pool user gets the same expected amount minus the pool's fee (pool-0: 1 percent) with the variance taken out; the reference pool holds no balance and pays from the coinbase split.
|
||||
|
||||
## The cards, measured
|
||||
|
||||
6 October 2026, rented single-card Linux boxes (docs/analysis/prover-tiers-real-cards.md, bench log 'Prover tiers on real cards: the rented fleet, 6 October 2026'): the 0.3.12 miner on the ten-field override, the miner alone on the card, nvidia-smi power at the same second. The RX 9070 XT row is the telemetry run of 5 October 2026 on a Windows rig (bench log 'Power, heat, fans and clocks, measured'); the Apple row is the Metal bench of 4 October 2026 (site/miner-bench.json, class v4). The class v4 rates on the live devnet run lower than the ten-field rates on the same card (RTX 5090: 124.2 MH/s class v4 against 98.5 on the rented box under a different driver and container, docs/bench-log.md); the table is re-generated at the testnet cut from that class.
|
||||
|
||||
| Tier | Card | MH/s | W | MH/W | Source |
|
||||
|---|---|---|---|---|---|
|
||||
| 8 GB | NVIDIA RTX 4060 Ti 8 GB | 19.07 | 72.6 | 0.263 | docs/analysis/prover-tiers-real-cards.md, RTX 4060 Ti 8 GB row |
|
||||
| 8 GB | NVIDIA RTX 4060 | 17.07 | owed | owed | docs/analysis/prover-tiers-real-cards.md, RTX 4060 row (the sampler read 0.0 W on that host: power OWED) |
|
||||
| 12 GB | NVIDIA RTX 3060 | 23.78 | 103.7 | 0.229 | docs/analysis/prover-tiers-real-cards.md, RTX 3060 row |
|
||||
| 12 GB | NVIDIA RTX 4070 | 24.99 | 91.1 | 0.274 | docs/analysis/prover-tiers-real-cards.md, RTX 4070 row |
|
||||
| 12 GB | NVIDIA RTX 5070 | 41.89 | 137.0 | 0.306 | docs/analysis/prover-tiers-real-cards.md, RTX 5070 row |
|
||||
| 16 GB | NVIDIA RTX 4060 Ti 16 GB | 17.58 | 72.3 | 0.243 | docs/analysis/prover-tiers-real-cards.md, RTX 4060 Ti 16 GB row |
|
||||
| 16 GB | AMD RX 9070 XT | 18.60 | 199.0 | 0.093 | docs/bench-log.md, 'Power, heat, fans and clocks, measured' (5 October 2026): 18.6 to 19.2 MH/s, 199 W read by the ADLX helper at 100 percent busy |
|
||||
| 24 GB | NVIDIA RTX 3090 | 37.79 | 228.8 | 0.165 | docs/analysis/prover-tiers-real-cards.md, RTX 3090 row |
|
||||
| 24 GB | NVIDIA RTX 4090 | 52.25 | 183.1 | 0.285 | docs/analysis/prover-tiers-real-cards.md, RTX 4090 row |
|
||||
| 32 GB | NVIDIA RTX 5090 | 98.48 | 258.2 | 0.381 | docs/analysis/prover-tiers-real-cards.md, RTX 5090 row (the rented box; 124.2 MH/s class v4 on a desktop, site/miner-bench.json) |
|
||||
| Apple | Apple M5 Max (40 GPU cores) | 26.70 | owed | owed | site/miner-bench.json, Apple M5 Max class v4 row, 4 October 2026 (no power reading: OWED) |
|
||||
|
||||
## IGN a day per card, solo, after the ramp (day 90)
|
||||
|
||||
Three network sizes, because income is a share of the network and nobody knows the network before it exists. 1 GH/s, 10 GH/s, 100 GH/s are reference sizes, not forecasts. The live network's estimate is on /live; divide by it.
|
||||
|
||||
| Tier | Card | at 1 GH/s | at 10 GH/s | at 100 GH/s |
|
||||
|---|---|---|---|---|
|
||||
| 8 GB | NVIDIA RTX 4060 Ti 8 GB | 124,414 | 12,441 | 1,244.1 |
|
||||
| 8 GB | NVIDIA RTX 4060 | 111,366 | 11,137 | 1,113.7 |
|
||||
| 12 GB | NVIDIA RTX 3060 | 155,142 | 15,514 | 1,551.4 |
|
||||
| 12 GB | NVIDIA RTX 4070 | 163,036 | 16,304 | 1,630.4 |
|
||||
| 12 GB | NVIDIA RTX 5070 | 273,293 | 27,329 | 2,732.9 |
|
||||
| 16 GB | NVIDIA RTX 4060 Ti 16 GB | 114,693 | 11,469 | 1,146.9 |
|
||||
| 16 GB | AMD RX 9070 XT | 121,348 | 12,135 | 1,213.5 |
|
||||
| 24 GB | NVIDIA RTX 3090 | 246,544 | 24,654 | 2,465.4 |
|
||||
| 24 GB | NVIDIA RTX 4090 | 340,882 | 34,088 | 3,408.8 |
|
||||
| 32 GB | NVIDIA RTX 5090 | 642,489 | 64,249 | 6,424.9 |
|
||||
| Apple | Apple M5 Max (40 GPU cores) | 174,192 | 17,419 | 1,741.9 |
|
||||
| Rig | 6 x NVIDIA RTX 4070 | 978,217 | 97,822 | 9,782.2 |
|
||||
|
||||
## Electricity a day, and the electricity cost of one mined IGN
|
||||
|
||||
Electricity a day = measured watts x 24 h x the tariff. The cost of one IGN divides that by the day-90 figure at 10 GH/s; at another network size it scales with the network (ten times the network, ten times the cost per coin). The three tariffs: USD 0.05 a kWh is cheap industrial power, USD 0.10 is a US retail rate, USD 0.25 is a UK retail rate at today's level (each approximate; tariffs move, the watt figures do not).
|
||||
|
||||
| Tier | Card | USD/day at 0.05/kWh | USD/day at 0.1/kWh | USD/day at 0.25/kWh | USD per IGN at 0.05/kWh | USD per IGN at 0.1/kWh | USD per IGN at 0.25/kWh |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 8 GB | NVIDIA RTX 4060 Ti 8 GB | 0.087 | 0.174 | 0.436 | 0.000007 | 0.000014 | 0.000035 |
|
||||
| 8 GB | NVIDIA RTX 4060 | owed | owed | owed | owed | owed | owed |
|
||||
| 12 GB | NVIDIA RTX 3060 | 0.124 | 0.249 | 0.622 | 0.00000802 | 0.000016 | 0.0000401 |
|
||||
| 12 GB | NVIDIA RTX 4070 | 0.109 | 0.219 | 0.547 | 0.00000671 | 0.0000134 | 0.0000335 |
|
||||
| 12 GB | NVIDIA RTX 5070 | 0.164 | 0.329 | 0.822 | 0.00000602 | 0.000012 | 0.0000301 |
|
||||
| 16 GB | NVIDIA RTX 4060 Ti 16 GB | 0.087 | 0.174 | 0.434 | 0.00000756 | 0.0000151 | 0.0000378 |
|
||||
| 16 GB | AMD RX 9070 XT | 0.239 | 0.478 | 1.19 | 0.0000197 | 0.0000394 | 0.0000984 |
|
||||
| 24 GB | NVIDIA RTX 3090 | 0.275 | 0.549 | 1.37 | 0.0000111 | 0.0000223 | 0.0000557 |
|
||||
| 24 GB | NVIDIA RTX 4090 | 0.220 | 0.439 | 1.10 | 0.00000645 | 0.0000129 | 0.0000322 |
|
||||
| 32 GB | NVIDIA RTX 5090 | 0.310 | 0.620 | 1.55 | 0.00000482 | 0.00000964 | 0.0000241 |
|
||||
| Apple | Apple M5 Max (40 GPU cores) | owed | owed | owed | owed | owed | owed |
|
||||
| Rig | 6 x NVIDIA RTX 4070 (a six-card rig of the 12 GB card, the rig's own power is six times the card's plus the host (the host is not measured here)) | 0.656 | 1.31 | 3.28 | 0.00000671 | 0.0000134 | 0.0000335 |
|
||||
|
||||
## What it means per tier, and what is being done
|
||||
|
||||
- **One 8 GB card.** NVIDIA RTX 4060 Ti 8 GB: 19.1 MH/s at 73 W, 12,441 IGN a day at 10 GH/s after the ramp, electricity 0.174 a day at USD 0.10 a kWh. Mines; proves only core-only shards (docs/analysis/prover-tiers-real-cards.md). The record says this tier is first under power in every exit (docs/analysis/mission/past.md section 3): the glide never halves it overnight, and the card games when the income goes. Being done: the Cards screen shows the expected MH/s, W and blocks a day before the first share (mission item 6).
|
||||
- **One 12 GB card.** NVIDIA RTX 5070: 41.9 MH/s at 137 W, 27,329 IGN a day at 10 GH/s after the ramp, electricity 0.329 a day at USD 0.10 a kWh. Mines and proves beside the miner on the patched server. Being done: the same Cards line; the proving tier sentence names the shard size the card takes.
|
||||
- **One 16 GB card.** NVIDIA RTX 4060 Ti 16 GB: 17.6 MH/s at 72 W, 11,469 IGN a day at 10 GH/s after the ramp, electricity 0.174 a day at USD 0.10 a kWh. Mines and proves; the AMD row is the measured 9070 XT, 199 W for 18.6 MH/s, so per watt it is about a fifth of the NVIDIA cards of its tier and the electricity cost per coin is the highest in the table. Being done: the AMD read-width experiment (docs/plans/read-width.md); until it lands the AMD tier is told its per-watt figure before it buys.
|
||||
- **One 24 or 32 GB card.** NVIDIA RTX 5090: 98.5 MH/s at 258 W, 64,249 IGN a day at 10 GH/s after the ramp, electricity 0.620 a day at USD 0.10 a kWh. The last GPU standing in every exit on record, and the only tier that proves the full shard beside the miner. Being done: the proving pool is the second income for this tier and the Earnings screen shows shards and IGN beside blocks.
|
||||
- **A rig.** 6 x NVIDIA RTX 4070: cards times the card figure; the host's own draw is not measured and is owed. Rigs followed income across chains inside days on every chain in the record; Igneum expects no loyalty and pays staying keys a vote (30 days of blocks).
|
||||
- **A pool user.** The same expected IGN minus the pool's fee, variance removed; pool-0 holds no balance and the member's own key is in every block it finds, so a pool's vote is the member's weight. Being done: the pool finished (mission item 11).
|
||||
- **Windows, Linux, macOS.** The rates above are Linux containers (NVIDIA) and Windows (AMD); the Mac row is Metal on Apple silicon with no power reading. The same card on another operating system is within the driver's margin, not measured here: owed per platform at the testnet cut.
|
||||
|
||||
## Owed
|
||||
|
||||
- Intel discrete (Arc): no measurement; the tier is listed with no number until one exists
|
||||
- RTX 4060 and Apple M5 Max wall power: the rows carry the rate and no electricity column
|
||||
- every row at class v4 on the testnet cut: the rates above are the ten-field class of 6 October 2026 on rented boxes
|
||||
- a rig host's own draw: a rig row is cards times the card figure, host excluded
|
||||
|
||||
## Check
|
||||
|
||||
`node tools/launch/income-tiers.mjs --check` (in `tools/ci/pre-push.sh`): the file equals the generator's output. `node --test tools/launch/income-tiers.test.mjs`: the schedule arithmetic against the fork's constants (100 MH/s on 100 GH/s after the ramp = 6,912 IGN a day, the figure in docs/analysis/horizon-2026-10.md; day 1 pays 10 percent; one month step pays 97.153 percent of the one before).
|
||||
566
docs/analysis/mission/future.md
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
# The last mission, lane 3: the far future, 2030 to 2036
|
||||
|
||||
Written 7 October 2026 by the future lane. Scope: the ten-year axes Horizon lane 7 did not cover, and the years 2030 to 2036 on the axes it did. Lane 7's rows to 2030 (`docs/analysis/horizon/frontier.md` 2.1 to 2.6) are taken as given and not repeated; where this lane disagrees, the row is named and the reason sourced. Every figure from memory is labelled approximate. Every figure from a source names it, with a URL in the sources list and the access date (all accessed 7 October 2026 unless stated). Times are UK. The arithmetic behind sections 2, 3, 4, 6 and 7 is `model.py` in this lane's scratch directory (`scratchpad/mission-future/`), reproduced in the tables.
|
||||
|
||||
The design this lane tests (horizon-2026-10.md section 1): an hourly random GPU program, a 256 MiB cache over a daily multi-GB dataset (2 GiB at genesis, doubling at years 4, 12, 28), latency-shadow work in a six-rung N ladder stepped by 90 percent miner signal, class v5 (dataset from chain state) as a candidate, miner-only finality with BLS vote keys weighted by 30 days of blocks, SP1 zkEVM proving by miners (the 20 percent pool), 100 IGN a block gliding to a 1 percent tail, no stake, no dev fund, no other chain in consensus.
|
||||
|
||||
## 0. Progress
|
||||
|
||||
| Time (UK) | State |
|
||||
|---|---|
|
||||
| 08:2x | Brief read: CLAUDE.md, frontier.md 2 and 6, algorithm.md 5, chip-model-v3.md 5, horizon-2026-10.md 1, finality-in-proof.md 4, 51-percent.md, bench-log rental entry |
|
||||
| 08:3x to 08:5x | 50 web searches (the session's search budget ran out at 50; the rest went by direct fetch), 27 primary fetches |
|
||||
| 08:5x | Model run (`model.py`): emission by year, chip thresholds, rental curve, heat credit, PQ bytes, wallet battery |
|
||||
| 08:5x to 09:00 | Sections 1 to 11 written, 566 lines, copy-law check clean (no em or en dashes, ASCII only) |
|
||||
| 09:00 | DONE. File handed to the coordinator. Nothing committed; nothing on the devnet touched; no build or measurement run |
|
||||
|
||||
The tiers, used in every row: home miner with one 8, 12, 16, 24 or 32 GB card; a rig; a pool user; on Windows, Linux, macOS; NVIDIA, AMD, Apple.
|
||||
|
||||
## 1. GPU and memory roadmaps, 2027 to 2036
|
||||
|
||||
### 1.1 The roadmap, sourced
|
||||
|
||||
| Item | What is announced or reported | Source | Label |
|
||||
|---|---|---|---|
|
||||
| HBM4 | Mass production at Samsung and SK hynix from February 2026; 2,048-bit interface, 8 Gbps a pin, about 2 TB/s a stack; 12-high 36 GB; SK hynix 16-high 48 GB from Q3 2026; Micron samples over 11 Gbps, 2.8 TB/s | TrendForce 9 Jan 2026; EE Times CES 2026; Astute Group | cited |
|
||||
| HBM4 price | About USD 550 a 36 GB stack, USD 15.3 per GB (factory gate) | siliconanalysts.com/tools/hbm-analysis | approximate (the site cites no primary) |
|
||||
| HBM4E | Late 2027 to 2028; 14 to 16 Gbps a pin, 3.6 to 4.0 TB/s a stack; 16-high; custom base dies on TSMC N3 (the GUC and TSMC "C-HBM4E" line, 12.8 GT/s by 2027); Samsung HBM4E samples May 2026 at 3.6 TB/s | TrendForce 23 Dec 2024; Tom's Hardware (TSMC/GUC); TechTimes 30 May 2026 | cited |
|
||||
| HBM on a consumer card | None announced. The Feynman datacentre architecture (2028) "supports HBM"; no consumer HBM part from NVIDIA, AMD or Intel on any roadmap found | Wikipedia Feynman page; the 2027 to 2028 rumour set | cited (absence) |
|
||||
| GDDR8 | No JEDEC standard. SK hynix's roadmap to 2031 lists "GDDR7-Next" for 2029 to 2031 | Tom's Hardware (SK hynix roadmap); TechSpot | cited |
|
||||
| GDDR7 devices | 2 GB ended at Micron (Sep 2026); 3 GB shipping at USD 60 to 70; 4 GB and 6 GB devices reported for 2027 to 2028 | chip-model-v3 5.1; wccftech; club386 | 3 GB cited, 4 and 6 GB rumour |
|
||||
| RTX 60 (Rubin GR20x) | Late 2027 slipped to 2028; GDDR7; the 6090 reported at 512-bit with 32 GB or 48 GB | TweakTown; wccftech; BigGo | rumour |
|
||||
| AMD RDNA 5 / UDNA | Mid-2027 to 2028; GDDR7 at 36 Gbps; flagship "AT0" 154 CUs, 36 GB on 384-bit, 1.7 TB/s, 380 W; shares a chiplet design with the next Xbox; GDDR7 support landed in the Linux driver | TweakTown; TechPowerUp; Tom's Hardware driver note | rumour, driver patch cited |
|
||||
| NVIDIA consumer chiplets | Nothing reported; Rubin consumer parts described as monolithic | the same rumour set | approximate |
|
||||
| Strix Halo class | Strix Halo: 256-bit LPDDR5X-8000, 256 GB/s. Medusa Halo (2027 to 2028): LPDDR6 on 256-bit (461 GB/s) or 384-bit (691 GB/s) | VideoCardz; hardware-corner.net | rumour |
|
||||
| Apple | M5 Max: up to 128 GB unified, 614 GB/s (40-core GPU), 460 GB/s (32-core); M5 Ultra Mac Studio August 2026 | Apple newsroom 3 Mar 2026 and Aug 2026 | cited |
|
||||
| LPDDR6 | JESD209-6 published July 2025; 2 sub-channels a die, 12 DQ each, 4 CA each; activate timings not public | JEDEC press release | cited; timings unknown |
|
||||
| DDR5 | 32 banks in 8 groups of 4 (x4/x8); JESD79-5D Nov 2025; tFAW a four-activate window | JEDEC; DDR5 core datasheet | cited |
|
||||
| The latency floor | "The latencies of three fundamental DRAM operations have not improved significantly in the past 18 years"; improvements "relatively stagnant for the last two decades" | Lee et al. (arXiv 1604.08041); Chang et al. (arXiv 1805.03154) | cited |
|
||||
|
||||
### 1.2 What it means for the memory-latency-bound hash
|
||||
|
||||
The hash advances one dependent 4-byte read per memory latency; the rate is activates per tFAW window times channels, and energy is per activate (chip-model-v3 5.3). Pin speed does not move it. So the ten-year question is only: do channels per watt per dollar move, and does tRC or tFAW move. The sources say tRC has been flat for about twenty years, and no DRAM roadmap to 2031 (SK hynix) names a row-cycle improvement. HBM4 doubles channels per stack (lane 7, 2.3). HBM4E adds pin speed and a custom base die, not channels. GDDR7-Next is 2029 to 2031 and unspecified. Consumer HBM does not exist on any roadmap to 2028.
|
||||
|
||||
| Year | Flagship consumer memory (projected) | Random reads per second, flagship (approximate) | Mid-tier card (12 to 16 GB) | Tier that wins or loses against 2026 |
|
||||
|---|---|---|---|---|
|
||||
| 2026 | 32 GB GDDR7, 512-bit, 16 devices | 17.5 G measured (5090) | 12 GB, 192-bit, 6 devices: about 6.5 G | baseline |
|
||||
| 2028 | 36 to 48 GB GDDR7, 384 to 512-bit (RDNA 5 rumour, RTX 60 rumour) | 16 to 21 G: the same, capacity adds no channels | 16 to 18 GB on the same channel count (3 GB devices): the same rate | nobody: a 2026 card keeps its rate against a 2028 card |
|
||||
| 2031 | 48 to 64 GB GDDR7-Next (SK hynix window) | unknown; if channels per device rise to 8 (approximate guess), 1.5 to 2x | 24 GB mid-tier on the same count | the 2026 owner falls to 0.5 to 0.7x of the new card, the normal GPU cadence |
|
||||
| 2036 | 64 to 96 GB (extrapolation of lane 7's 1.167 a year); HBM on a halo consumer part possible but unannounced | 2 to 4x of 2026 if a consumer HBM4-class part ships; otherwise 1.5 to 2x | 32 GB mid-tier | the 8 and 12 GB tiers are gone from the installed base (Steam trend, algorithm.md 5.6), not from the hash |
|
||||
|
||||
Apple and APUs: the M5 Max's 614 GB/s is a 512-bit LPDDR5X bus (approximate); LPDDR activates are the same DRAM physics, so the Apple tier stays "0.78 uJ per hash, 21 W" class (algorithm.md 5.3a), the best per joule and the worst per dollar (USD 129 per MH/s). Medusa Halo on 384-bit LPDDR6 would be a 24 to 36 channel part (approximate: 2 sub-channels a die): a mid-tier card's read rate at laptop watts. Consequence per tier: Apple and APU miners stay the per-joule leaders and never the per-dollar ones; nothing in the hash changes that in ten years.
|
||||
|
||||
### 1.3 The cache, the dataset schedule and the ladder, re-read against the roadmap
|
||||
|
||||
| Design item | Roadmap fact | Verdict | Recommendation |
|
||||
|---|---|---|---|
|
||||
| 256 MiB cache "above every GPU's on-die cache" (the spec's rule) | 5090 L2 96 MB, GB202 128 MB; MI300X carries 256 MB Infinity Cache (datacentre, approximate from memory); RDNA 3's 7900 XTX 96 MB; no consumer part at 256 MB announced | Safe to 2028. At risk from 2029 to 2031 if a consumer part ships a 256 MB last-level cache, which the datacentre already does | Add a cache-size rung to the era-draw ladder beside N (256 to 512 MiB), stepped by the same 90 percent signal, in place of the fixed year-4 doubling alone; the trigger is a shipped consumer part with LLC at or over the cache size |
|
||||
| Dataset 2 GiB, doubling at years 4, 12, 28 | Card memory 32 GB now, 48 to 64 GB by 2030 (lane 7); one HBM3 stack holds 24 GB | Right. The dataset is not a lever against the stored-dataset chip (chip-model-v3 5.7) and never binds a tier before year 12 (algorithm.md 5.6) | Hold the schedule; the public card-lifetime sentence should carry the prover footprint, not the dataset (algorithm.md 5.6's one change) |
|
||||
| N ladder rungs measured on 2026 cards | The honest card's bind point moves with each generation: a 2028 card with the same read rate and 1.5x the ALUs binds at a higher N; HBM4 doubles the chip's rate per stack | Rungs are a 2026 measurement. They are the right shape and the wrong numbers for 2029 | Re-measure the rungs per card generation (the Steam top-10 cards each era) and publish the bind points; the signal mechanism already lets miners refuse a rung their cards cannot hold, so no genesis change, only a measurement duty written into the era-draw docs |
|
||||
| C-HBM4E custom base die (2027 to 2028) | The base die under the stack becomes a logic die on N3 that a customer designs (TSMC and GUC) | This is the f = 1 chip's controller moved under the memory: the chip-model's "controller and PHY die beside it, 10 W, USD 50, plus a USD 200 one-stack interposer" row (5.3) collapses into the base die. Lane 7's 2.3 did not price it | Disagreement with lane 7 row 2.3 "HBM4 one stack (2028)": the controller and interposer lines fall toward zero, so the HBM4 chip's dollars per MH/s fall below the USD 2.8 GDDR7 figure by 2028, approximate. The answer is unchanged in kind (N, the price per joule) and larger in degree; section 2 carries it |
|
||||
|
||||
Per tier, section 1 in one line each: 8 GB (mines to year 12, never proves beside its miner); 12 GB (mines to year 28, loses mine-and-prove at year 4); 16 GB (mines and proves to year 12); 24 and 32 GB (unconstrained to 2036 on every roadmap found); rig (the rate per card is flat through 2028, so a 2026 rig is a 2028 rig); pool user (the pool's share tracks the installed base, which loses the 8 GB tier by 2030); Windows and Linux (no change); macOS (per-joule best, per-dollar worst, both for ten years); NVIDIA (channel count flat to 2028); AMD (RDNA 5 brings GDDR7, a 36 GB flagship: AMD's first competitive random-read part since the 9070 XT's 2.4 to 2.7 G); Apple (as macOS).
|
||||
|
||||
## 2. The ASIC maker's economics, 2026 to 2036
|
||||
|
||||
### 2.1 Inputs
|
||||
|
||||
| Input | Value | Source |
|
||||
|---|---|---|
|
||||
| Mask set, total NRE: TSMC 28 nm | USD 1 M, 1.8 M | siliconanalysts.com/data/wafer-pricing (Sep 2026) |
|
||||
| 16 nm | 1.8 M, 3.2 M | same |
|
||||
| 7 nm | 3.5 M, 5.5 M | same |
|
||||
| 5 nm | 6.5 M, 10 M | same |
|
||||
| 3 nm | 15 M, 22 M; design cost of a 3 nm chip USD 400 to 600 M all-in | same; siliconanalysts tsmc-3nm-cost |
|
||||
| 2 nm | masks USD 15 to 30 M; design cost quoted at USD 724 M | semiwiki thread; siliconanalysts | approximate |
|
||||
| A16, A14 | no mask quote found; "capex per 1,000 wafers at A14 higher than N2" | semiwiki A14 thread | approximate |
|
||||
| Wafer, 300 mm | 28 nm 3,000; 16 nm 5,500; 7 nm 9,500; 5 nm 20,000; 3 nm 20,000 (range to 27,000) | siliconanalysts wafer-pricing | cited |
|
||||
| Leading-edge tapeout, all-in | USD 30 M to 100 M+ at 3 to 5 nm; 5 to 30 M at 7 to 28 nm | siliconanalysts tapeout guide, 1 Mar 2026 | cited |
|
||||
| The f = 1 chip | 166 MH/s, 78 W bare, 228 W with a 150 W shadow core at k = 1; USD 470 of memory, controller and board; USD 2.8 per MH/s | chip-model-v3 5.4, lane 7 2.3 | model |
|
||||
| The honest card | 5090 at class v4: 3.27 uJ, 431 W cap, 132 MH/s; USD 1,999 MSRP | algorithm.md 5.3a | measured |
|
||||
| Emission (model) | 100 IGN a block, 90-day ramp from 10 percent, monthly glide at 2.9 percent, tail 1 percent a year from year 11.4 | tail-emission.md | model |
|
||||
| Rental equilibrium | hash joins until rent equals subsidy: 39, 156, 780 GH/s at USD 0.005, 0.02, 0.10 per IGN | security-budget.md via 51-percent.md | model |
|
||||
|
||||
Emission by year from the model (IGN, approximate): year 1 2.32 B, year 2 1.87 B, year 3 1.31 B, year 4 0.92 B, year 5 0.65 B, year 6 0.46 B, year 7 0.32 B, year 8 0.23 B, year 9 0.16 B, year 10 0.11 B; supply 4.18 B at the end of year 2, 7.07 B at year 5, 8.33 B at year 10.
|
||||
|
||||
### 2.2 The decision tree, written out
|
||||
|
||||
The maker chooses a target share s of the hash and a node. Revenue over the chip's two-year life is s x E2 x p, where E2 is the two-year emission and p the IGN price. The fleet needed is s/(1 - s) x H(p), and at the rental equilibrium H(p) = 7.8 M MH/s per USD of price, so the fleet's cost is also linear in p: 0.43 x 7.8 M x USD 4.64 per MH/s (the chip's two-year cost per MH/s with power at USD 0.05 per kWh, model) against two-year revenue per MH/s of 0.0117 x 17,520 = USD 205. The fleet term is 2 percent of revenue and drops out. The project pays when p is over p* = C_proj / (s x E2), and the market cap at which it pays is p* x supply. At s = 0.30 (an economic miner just under the veto third):
|
||||
|
||||
| Node (project all-in) | Years 1 to 2 (E2 = 4.18 B) | Years 3 to 4 (2.24 B) | Years 5 to 6 (1.10 B) | Years 7 to 8 (0.55 B) | Years 9 to 10 (0.27 B) |
|
||||
|---|---|---|---|---|---|
|
||||
| 28 nm controller only, no shadow core (USD 5 M) | p* 0.0040, cap USD 17 M | 0.0075, 48 M | 0.0151, 114 M | 0.0306, 247 M | 0.0620, 517 M |
|
||||
| 28 nm controller + N5 shadow core (USD 30 M) | 0.0239, 100 M | 0.0447, 287 M | 0.0906, 682 M | 0.1836, 1,481 M | 0.3718, 3,099 M |
|
||||
| N3 single die, controller + shadow + lanes (USD 60 M) | 0.0478, 200 M | 0.0895, 574 M | 0.1813, 1,363 M | 0.3672, 2,961 M | 0.7437, 6,198 M |
|
||||
| N2 (USD 150 M, 2026 quotes) | 0.1195, 500 M | 0.2237, 1,436 M | 0.4532, 3,408 M | 0.9179, 7,403 M | 1.8592, 15,495 M |
|
||||
| A16 / A14 (USD 250 M, extrapolated) | 0.1992, 833 M | 0.3729, 2,393 M | 0.7553, 5,680 M | 1.5298, 12,339 M | 3.0987, 25,825 M |
|
||||
|
||||
Reading. The stored-dataset chip without a shadow core pays at a USD 17 M market cap in the first two years, because its project is a 28 nm controller (chip-model-v3 5.6) and the chip's hour costs 56x less than rented hash. The class v4 shadow core forces an N5-class die (30 mm^2 at N = 100,000, lane 7) and lifts the bar 6x to USD 100 M. The glide lifts every bar about 2.3x per two years, so by years 9 to 10 the same N5 chip needs a USD 3.1 B cap. Nothing here needs N2 or A16: the chip is memory, and a leading node buys it nothing. So the "ASIC maker's economics at every node" collapses to two nodes, 28 nm and N5, and the N ladder is what moves between them.
|
||||
|
||||
Minimum volume to break even against buying cards (saving USD 2,131 a chip over two years against 5090s at the same hash, model): 28 nm bare 2,346 chips (0.39 TH/s); 28 nm + N5 shadow 14,077 chips (2.34 TH/s); N3 28,155 (4.67 TH/s); N2 70,387 (11.7 TH/s); A14 117,312 (19.5 TH/s). Against the rental-equilibrium hash (39 to 780 GH/s at today's three price inputs) the 2.34 TH/s break-even fleet is 3x to 60x the whole network: the chip only pays once the price is high enough for the network to be a few TH/s, which is the p* column above said another way.
|
||||
|
||||
### 2.3 The memory-controller chip against HBM4, per joule and per dollar, 2026 to 2036
|
||||
|
||||
| Year | Memory the chip buys | Chip uJ per hash at N = 100,000, k = 1 (approximate) | Honest 5090-class uJ | Edge per joule | Chip USD per MH/s | What moves it |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 2026 | GDDR7, 16 x 2 GB | 1.37 | 3.27 | 2.4x | 2.8 | the measured row (algorithm.md 5.3a: 2.1x at the 5090's 431 W cap) |
|
||||
| 2028 | HBM4, one stack 36 GB, C-HBM4E base die as the controller | 1.33 (algorithm lane's correction of lane 7's 1.11) | 3.3 (a 2028 card at the same read rate) | 2.5x | 2.0 to 2.5 (the interposer and controller rows fall into the base die; approximate) | HBM4 price USD 550 a stack falls as HBM4E takes the premium (approximate) |
|
||||
| 2031 | HBM4E or HBM5 (SK hynix lists HBM5 on the 2029 to 2031 window) | 1.2 to 1.3 at the same N; 0.9 at N = 100,000 if activates per channel double again | 3.0 to 3.3 | 2.5x to 3.5x | 1.5 to 2.0 | activate parallelism per stack; unsourced beyond HBM4 |
|
||||
| 2036 | the same class | the per-joule edge is bounded below by N x 11 pJ x k, so at N = 330,000 and k = 1 the chip pays 3.6 uJ of program work whatever its memory | 5.0 at 330,000 (the 5090 is compute-bound there) | 1.3x | 1.5 | N and k only |
|
||||
|
||||
Does the chip get cheaper or dearer per joule as HBM prices fall: dearer in dollars relative to the GPU through 2027 (lane 7 2.2, memory is 70 percent of its bill), cheaper from 2028 when the base die absorbs the controller, and flat per joule, because per joule is set by activates and by N. Per tier: the home 5090 owner stays inside 2.1x to 2.5x of the chip at N = 100,000 through 2031 and inside 1.3x at N = 330,000; the 4070-class 12 GB owner the same within 10 percent; the AMD 9070 XT owner sits at 5x to 8x behind the chip at every rung (its measured 10.6 uJ) and is the first tier a chip displaces; the Apple tier sits under the chip's per-joule line at every rung. The rig owner is a 5090 owner times eight. The pool user inherits the pool's card mix.
|
||||
|
||||
### 2.4 The FPGA route
|
||||
|
||||
AWS F2 (f2.6xlarge, VU47P, 16 GB HBM2, USD 1.98 an hour on demand, USD 0.66 spot) is the measurement the algorithm lane planned (algorithm.md 5.1); nothing has run. The tightened range stands: 2.3 to 2.9 G reads a second a card, 0.30x to 0.47x of the 5090 per watt, at USD 4,000 to 5,000 a card (approximate). Versal HBM and Agilex 7 M-series carry HBM2e (faster pins, the same tFAW), so they sit in the same row. An HBM4-based FPGA (none announced) would carry HBM4's channel count and the 0.3x to 0.5x row would become 0.6x to 1.0x (approximate, derived). Verdict: no FPGA displaces any tier to 2031; the F2 hour should still run, because the per-stack activate rate it measures is the input every row above rests on.
|
||||
|
||||
Recommendation for section 2: (1) the N ladder at genesis, as lane 2 and lane 7 said, with the bind-point re-measurement duty of 1.3; (2) write the p* table into the public threat model with the sentence "a stored-dataset chip pays at about USD 100 M of market cap in year 1 and about USD 700 M in year 5 (model, approximate)"; (3) no genesis parameter changes for N2 or A16, because the chip never needs them.
|
||||
|
||||
## 3. AI compute demand and the GPU supply
|
||||
|
||||
### 3.1 The numbers
|
||||
|
||||
| Row | Value | Source | Label |
|
||||
|---|---|---|---|
|
||||
| Datacentre GPUs shipped 2023 | 3.85 M units (NVIDIA 3.76 M, AMD 0.5 M, Intel 0.4 M) | TechInsights via HPCwire, 10 Jun 2024 | cited |
|
||||
| Datacentre GPUs 2024, 2025 | USD 123 B of GPUs and accelerators in 2024, USD 207 B in 2025 (Omdia); NVIDIA estimated 5.2 M Blackwell GPUs in 2025; GB200 cabinet forecasts cut to 25,000 to 35,000 (2.5 M GPUs) | Omdia Aug 2025; Tom's Hardware | cited, unit counts secondary |
|
||||
| Installed datacentre fleet by end-2026 | 15 to 20 M Hopper and Blackwell class units (sum of the rows above) | arithmetic | approximate |
|
||||
| Consumer AIB shipments | Q2 2026 12.5 M units, +10 percent QoQ, +6.6 percent YoY; H1 2026 24.3 M; NVIDIA about 90 percent | Jon Peddie Research Q2 2026 | cited |
|
||||
| Used H100 | USD 18,000 to 22,000 in 2026; residual 40 to 75 percent at 36 months, 25 to 35 percent at 60+ months; A100 80 GB USD 12,000 to 18,000 | mercatus-ai.com (verified 23 Jun 2026); intuitionlabs | cited, secondary |
|
||||
| H100 rental | USD 1.49 (Vast.ai hosts) to 6.98 an hour; was over USD 7 in early 2024; spot about USD 1.00 | cloudzero; spheron; shattered.io (2026) | cited |
|
||||
| Consumer card rental | 5090 USD 0.21 to 0.44 an hour (Vast.ai, 6 Oct 2026); 4090 0.28 to 0.60 | lane 7 2.4 | cited |
|
||||
| Igneum hash rental | USD 0.0117 per MH/s-hour (RunPod community pods, 38 pods, 1,748 MH/s for USD 20.44 an hour); 8x 4090 rig USD 0.0129; a 5090 pod 98 to 128 MH/s for USD 0.41 to 0.74 | bench-log, 6 Oct 2026 | measured |
|
||||
|
||||
### 3.2 The used-GPU flood, 2027 to 2030
|
||||
|
||||
Hopper fleets bought in 2023 to 2024 reach the 36-month residual cliff in 2026 to 2027 and the 60-month floor in 2028 to 2029. Can they mine Igneum: the hash is memory-latency-bound, so an H100 is its five HBM3 stacks (80 GB) and an A100 its five HBM2e stacks. At chip-model-v3's unmeasured HBM3 ceiling (10.7 G reads a second a stack) an H100 reads 53 G, 3x a 5090; at the JEDEC tFAW ceiling (2.3 G a stack) 11.5 G, 0.65x. Nobody has measured it; the fleet measured A4000, A5000, L4, 3090 and 4090 (prover-tiers-real-cards.md), not an HBM part. The honest statement: an H100 mines Igneum at 0.65x to 3x of a 5090 at 700 W, which is 0.3x to 1.4x per watt (approximate, both ends unmeasured).
|
||||
|
||||
| Scenario | Hash it adds | Against the rental equilibrium (39 to 780 GH/s) | What the ladder does |
|
||||
|---|---|---|---|
|
||||
| 1 percent of a 1 M retired H100 fleet (10,000 cards) at 1x a 5090 | 1.3 TH/s | 2x to 35x the whole network | nothing: the shadow binds compute, and an H100 has 4x a 5090's ALUs per read; it holds every rung |
|
||||
| 10 percent | 13 TH/s | 17x to 330x | the same |
|
||||
| Rental at USD 1.49 an hour for 0.65x to 3x of a 5090 | USD 0.0045 to 0.021 per MH/s-hour | at or above today's 0.0117: no cheaper than consumer pods at list price | n/a |
|
||||
| Idle-time rental (the owner's marginal cost is power) | USD 0.00016 to 0.0007 per MH/s-hour at USD 0.05 per kWh | 17x to 70x under today's rent | n/a |
|
||||
|
||||
The ladder is a joule argument against a fixed-datapath chip; against a GPU with more ALUs it is silent. The used-fleet question is a price question only.
|
||||
|
||||
### 3.3 The ten-year rental curve and the 51 percent table
|
||||
|
||||
The H100 rate fell about 45 percent a year from early 2024 to 2026 (USD 7+ to about 2); consumer-card rent fell as purchase prices rose (lane 7 2.4). Three curves for USD per MH/s-hour from today's 0.0117 (model):
|
||||
|
||||
| Year | At -20 percent a year | At -30 percent a year | At -45 percent a year |
|
||||
|---|---|---|---|
|
||||
| 2028 | 0.0075 | 0.0057 | 0.0035 |
|
||||
| 2031 | 0.0038 | 0.0020 | 0.0006 |
|
||||
| 2036 | 0.0013 | 0.0003 | 0.00003 |
|
||||
|
||||
What it does to `51-percent.md`: nothing to the dollar cost of any attack, because every row there is priced at the rental equilibrium, where rent equals subsidy. A cheaper rent means more hash joins until rent equals subsidy again: at 2036's -30 percent curve the equilibrium hash is 35x today's at the same IGN price, and the attack costs the same twelve days of emission. What the curve changes is the home card's share of the subsidy, which falls 35x with the hash, and the supply ceiling of the rental market, which stays the real limit (the market gave zero pods when asked for twenty, bench-log). Update the 51-percent price basis each year from a measured rental, not from a curve.
|
||||
|
||||
### 3.4 Per tier: does a home card stay competitive against an idle datacentre card
|
||||
|
||||
Cost per MH/s-hour, electricity only, a 5090-class card at 3.27 uJ (model):
|
||||
|
||||
| Owner and power price | USD per MH/s-hour | Margin under today's 0.0117 rent | Margin if idle datacentre hash sets the rent (0.00016) |
|
||||
|---|---|---|---|
|
||||
| UK home, 26.32 p (Ofgem cap, Q4 2026) | 0.00114 | 10x | loses 7x |
|
||||
| Germany home, EUR 0.387 | 0.00137 | 9x | loses 9x |
|
||||
| US home, 17.7 c (EIA, H2 2025) | 0.00058 | 20x | loses 4x |
|
||||
| Texas industrial, 5 c (approximate) | 0.00016 | 72x | break-even |
|
||||
| Paraguay, 4.4 to 6 c (ANDE crypto tariff, Decree 7824/2022, contracts end 31 Dec 2027) | 0.00016 | 72x | break-even |
|
||||
| Iran, licensed, about 1 c | 0.00003 | 358x | wins |
|
||||
|
||||
The home card is competitive while the marginal supplier of hash is a rented consumer pod at list price (today). It is not competitive the day the marginal supplier is an idle datacentre card on industrial power, and the used-fleet flood of 3.2 makes that day a price event, not a technology event. Per tier: 8 and 12 GB home cards lose first (their uJ is 1.3 to 1.7x worse than the 5090's); 24 and 32 GB cards last; a rig is a home card eight times, on the same power price; a pool user's payout tracks the pool's share, which falls with the home share; Windows and Linux are the same; macOS at 0.78 uJ (M5 Max) holds a 4x power edge over the 5090 and loses last of all the home tiers; NVIDIA and AMD as their uJ rows (algorithm.md 5.3a); Apple as macOS.
|
||||
|
||||
What Igneum should do: the reward rule that prices rented hash out (lane 7's 3.1, weight-aged keys paid more) is the only lever in the protocol; the earnings page should show the miner's own cost per MH/s-hour against the network's implied rent, so a UK miner sees the day the line crosses; and the H100 and A100 random-read rate should be measured on one rented card this month (two hours, under the measure lock), because every row of 3.2 rests on it.
|
||||
|
||||
## 4. Energy, heat and the home
|
||||
|
||||
### 4.1 Prices and forecasts
|
||||
|
||||
| Region | Household price 2026 | Ten-year direction | Source |
|
||||
|---|---|---|---|
|
||||
| UK | 26.11 p per kWh (Jul to Sep 2026 cap), 26.32 p (Oct to Dec 2026) | Cornwall Insight's wholesale path falls to GBP 83 per MWh by 2029 ("over GBP 40 above historic"); retail scenarios 22 to 42 p by 2030 | Ofgem; Cornwall Insight (Jan 2024); solarpanelsforfactories (secondary) |
|
||||
| EU | EUR 28.96 per 100 kWh average H2 2025; Ireland 40.42, Germany 38.69, Belgium 34.99; Hungary 10.82, Malta 12.82, Bulgaria 13.55 | The Commission's Electrification Action Plan (17 Jul 2026) targets electricity at most 2.5x gas for households by 2030 | Eurostat 5 May 2026; Commission |
|
||||
| US | 17.7 c average H2 2025; "continue steady increase" | AEO2025 reference: 13 c (2024, a different basis) to over 20 c by 2050 | EIA |
|
||||
| Cheapest mining regions | Iran about 1 c (licensed); Ethiopia 2 to 5.3 c; Paraguay 4.4 to 6 c; Kazakhstan about 4 c; Nigeria 4.8 c hosted | spark.money; oneminers; hashrateindex Paraguay (4 May 2026) | secondary |
|
||||
|
||||
### 4.2 Home mining as heating
|
||||
|
||||
| Product or trial | Facts | Source |
|
||||
|---|---|---|
|
||||
| Heatbit Trio, Maxi, Maxi Pro | USD 849 (10 TH/s, 400 W mining + 1,100 W resistive) to USD 1,499 (60 TH/s, 1,500 W); seasonal BTC USD 300 to 420 (Heatbit's own figure); Wired's review: mining covers 30 to 40 percent of electricity at 12 to 15 c per kWh | miningboard.com; techbuzz (5 Apr 2026) |
|
||||
| 21energy, MintGreen, HotMine | convector radiators 250 to 2,700 W; hydronic boilers for radiators and hot water | miningboard.com |
|
||||
| Qarnot "radiateur numerique" | 100 RIVP social-housing flats in Paris 15e heated by compute radiators from 2013; Qarnot moved to boilers; the model is "the building pays the capital, heat is free" | fr.wikipedia Qarnot; maisonapart |
|
||||
|
||||
The economics per kWh (model): a 5090 at the 431 W cap is a 0.43 kW heater. Credit the heat at the gas price delivered through a 90 percent boiler, or at a heat pump's electricity (COP 3):
|
||||
|
||||
| Region | Electricity | Heat credit, gas | Heat credit, heat pump | Effective price in the heating season |
|
||||
|---|---|---|---|---|
|
||||
| UK | 26.3 p | 7.0 p (27 percent) | 8.8 p (33 percent) | 17.5 to 19.3 p |
|
||||
| Germany | 38.7 c | 13.3 c (34 percent) | 12.9 c (33 percent) | 25.4 to 25.8 c |
|
||||
| US | 17.7 c | 5.6 c (31 percent) | 5.9 c (33 percent) | 11.8 to 12.1 c |
|
||||
|
||||
A third off the power bill for the 1,500 to 2,000 heating hours a year (approximate), everywhere. It moves a UK home miner from 7x to 5x the Texas industrial price, not to parity. Consequence per tier: it matters most to the tiers with the worst uJ (8 and 12 GB, AMD) and least to Apple (21 W is not a heater). Recommendation: a heat mode in Ember (run the miner only while a room thermostat or schedule calls, power cap set to the room's load, the hourly program unchanged), which costs nothing in protocol and is the one feature home-mining heaters ship.
|
||||
|
||||
### 4.3 Grid balancing
|
||||
|
||||
ERCOT paid Riot USD 31.7 M in August 2023 (24.2 M curtailment credits plus 7.4 M demand response) and Riot booked USD 30.6 M of power-curtailment credits in Q3 2025, up 147 percent on the year (ABC13; Riot releases). The programmes name "large flexible customers"; a home GPU is not one. Home aggregators exist (the UK's supplier demand-flexibility sessions, US utility programmes; approximate, from memory) and pay per kWh shed against a baseline. A fleet of Ember miners is a shed-able load only if a signal reaches it. Recommendation: a curtailment input in Ember (a webhook, a schedule, a price threshold from a public spot feed) that pauses the miner and resumes it; the protocol sees a key that stops voting for an hour, which the LEAVE item of 0.3.16 already prices as nothing (vote-or-burn.md: no burn). Per tier: a pool user's pool must pass the signal down; a rig is the only home tier big enough to enrol directly.
|
||||
|
||||
### 4.4 The rules by region
|
||||
|
||||
| Region | Rule | Source | What it means for an Igneum home miner | What Igneum builds |
|
||||
|---|---|---|---|---|
|
||||
| EU, MiCA | White papers must state the consensus mechanism's climate impacts; CASPs must publish the sustainability indicators per asset (Delegated Regulation (EU) 2025/422; mandatory kWh a year, more above 500,000 kWh); Article 142 report on environmental impact and "minimum sustainability standards"; the 2022 Parliament vote dropped a PoW ban; the 2026 MiCA review consultation (reply by 31 Aug 2026) asks only how appropriate the disclosure regime is (Q53) and nothing on PoW | MiCA; 2025/422; the consultation PDF (fetched) | nothing on the miner; the exchanges that list IGN need the kWh figure | publish Igneum's own 2025/422-format indicators from the hash-rate and the measured uJ, updated each era |
|
||||
| Norway | ban on new PoW mining data centres from autumn 2025; data-centre registry; existing sites run | CoinDesk 23 Jun 2025 | home mining untouched; no new hosting | nothing |
|
||||
| Sweden | data-centre electricity tax relief removed July 2023 (SEK 0.006 to 0.36 per kWh); SEK 500 M of back-tax on nine firms 2024 to 2026 | CoinDesk 14 Apr 2023; crypto.news | home miners already paid full tax; hosting is dead | nothing |
|
||||
| Russia | mining banned in 10 regions 1 Jan 2025 to 15 Mar 2031; Moscow region from 15 Aug 2026 to 2032; seasonal bans in Irkutsk, Buryatia, Zabaikalsky; no new regions in 2026 | TASS; Cryptopolitan | a region check at install; the 2024 law's 6,000 kWh a month household allowance (approximate, from memory) covers one card | a region prompt in Ember with the banned list |
|
||||
| Kazakhstan | mining legal outside the AIFC (Nov 2025 law); "strategic mining" rules from 1 Aug 2026 with a share to the national reserve | Caspian News; KuCoin | licensed industrial; a home card is below any threshold found | nothing |
|
||||
| Paraguay | ANDE crypto tariff USD 44.33 per MWh (Decree 7824/2022), lifted toward 5.1 to 6 c; every crypto contract ends 31 Dec 2027 | hashrateindex 4 May 2026 | the cheapest hydro region closes to new load in 2028 | nothing |
|
||||
| Iran | licensed mining at a mining tariff; seasonal shutdowns in power shortages (approximate) | ainfp.org; MEXC | the cheapest power on earth and the least reliable | nothing |
|
||||
| China | illegal; joint Notice 6 Feb 2026 reaffirmed; one report of Sichuan, Inner Mongolia, Xinjiang reopening from 1 Jan 2026 is uncorroborated | lightspark; egw.news (single source) | a Chinese home miner runs at legal risk | the region prompt |
|
||||
| US, federal | SEC staff statement 20 Mar 2025: PoW mining, solo and pooled, is not a securities offering; DAME 30 percent excise proposed 2023, 2024, 2025 budgets, never enacted; EIA's emergency survey (Jan 2024) blocked in court (Mar 2024); CLARITY failed cloture 49 to 50 on 15 Sep 2026; GENIUS Act (stablecoins) 2025 | Dechert; Blockworks; Fortune; Orrick 2 Oct 2026 | nothing on the miner | nothing |
|
||||
| US, states | Texas, Kentucky, Wyoming pro; New York's fossil-PoW moratorium (2022, approximate); California's DFAL licensing from 1 Jul 2026 reaches crypto businesses, not a home card | sazmining; crypto.news | a home miner is not a licensee anywhere found | the region prompt |
|
||||
|
||||
Per-region electricity-price prompt: the earnings page should default the price per kWh from a public table by country (the Eurostat, Ofgem and EIA rows above), let the miner edit it, and show the miner's own break-even hash share, because the number that ends a home miner is the bill, not the regulation.
|
||||
|
||||
## 5. Zero-knowledge proving cost, 2026 to 2036
|
||||
|
||||
### 5.1 The numbers today
|
||||
|
||||
| Row | Value | Source | Label |
|
||||
|---|---|---|---|
|
||||
| EF real-time proving target (10 Jul 2025) | P99 under 10 s, capex under USD 100 K, under 10 kW, open source, 128 bits (100 accepted at first), proof under 300 KiB, no trusted setup; written for solo stakers "from home" on a 10 kW supply | blog.ethereum.org | cited |
|
||||
| Ethproofs 2025 review (6 Dec 2025) | USD 1.69 to under a penny a proof in nine months; 7 zkVMs, 15 provers, about 200,000 blocks; four teams at sub-10 s P99; single-GPU proving from 16 min to under 60 s; 2026 target sub-8 s P99 and kWh a proof as the metric | hackmd willcorcoran | cited |
|
||||
| SP1 Hypercube | 99.7 percent of blocks under 12 s on 16 x RTX 5090, cluster under USD 100 K; about USD 0.02 a block single-node | Succinct blog; The Block | cited |
|
||||
| Pico Prism | 99.9 percent under 12 s on 64 x 5090 | Brevis | cited |
|
||||
| Cysic Venus | 7.4 s a block on 24 GPUs (type unstated); ZK ASIC claims (1.33 M Keccak a second, 50x energy) with no shipping date | bex.co 17 Apr 2026 | cited, ASIC unverified |
|
||||
| ZisK | p99 9.62 s on 4 x 5090 | GitHub comparative analysis, Sep 2026 | secondary |
|
||||
| Airbender | 51 s average on one RTX 4090, under a cent | bex.co Jan 2026 | secondary |
|
||||
| RISC Zero | R0VM 2.0: 35 min to 44 s a block (Dec 2025) | wavect; RISC Zero blog | secondary |
|
||||
| Jolt | Lattice Jolt (9 Sep 2026): 2 to 3x faster, 65 to 80 KB proofs, 2 M cycles a second CPU, over 10 M with a GPU, post-quantum | cryptobriefing | cited |
|
||||
| Cost a block on the tracker | about USD 0.005 (Sep 2026) | lane 7 2.6 | secondary |
|
||||
| Hardware provers | Cysic (C1, ZK-Air, ZK-Pro), Fabric (VPU), Ingonyama (ICICLE; Accseal Leo ASIC in ICICLE v3), Irreducible (FPGA clusters, Binius), Supranational; "10 to 100x on MSM and NTT" claims | h33.ai survey; Ingonyama; Cysic docs | claims, no shipped benchmark on a zkVM block |
|
||||
| Igneum's own | 11 rented cards: shard beside the miner 10.7 s (5090) to 37.5 s (3060); alone 4.8 to 18.4 s; SP1 compressed verify 0.032 s on a Mac core | prover-tiers-real-cards.md; bench-log 5 Oct | measured |
|
||||
|
||||
### 5.2 The trend line to 2036 and the 20 percent pool
|
||||
|
||||
Lane 7's 33x a year is software catching hardware and cannot hold. Hardware alone is 1.5x a year. The EF's own 2026 metric is kWh a proof, which is the right axis for a miner-prover: a shard that costs a joule of GPU time is paid from the pool and competes with the same joule in the lottery.
|
||||
|
||||
| Year | Block proof cost on the open market (USD, approximate) | Hardware that proves an Ethereum block in real time | What proves an Igneum shard (4.7 M cycles) under 10 s | Does a home 12 GB card have a place |
|
||||
|---|---|---|---|---|
|
||||
| 2026 | 0.005 to 0.02 | 16 x 5090 (SP1), 4 x 5090 (ZisK) | 5090 beside its miner (10.7 s), every card alone | yes, alone or hand-off (prover-tiers-real-cards.md) |
|
||||
| 2028 | 0.001 to 0.005 | 1 to 4 consumer cards; first ASIC or VPU boards if any ship (none has) | every card from the 3060 at 3x a year (lane 7's table); 12 GB beside the miner at 3x, not at 1.5x | yes alone; beside the miner only under 3x |
|
||||
| 2031 | 0.0003 to 0.001 | one consumer card, or a prover ASIC at 10 to 50x per joule if the claims land | a 12 GB card in 1 to 3 s | yes, but a prover ASIC would take the external job market first (dollar-priced jobs go to the cheapest joule), and the pool second only if shards are open to anyone |
|
||||
| 2036 | under 0.0001 | commodity | any card | the pool's economics are the lottery's: whoever holds vote weight is drawn (sortition by weight), so the home card keeps its share of the pool whatever an ASIC does to the open market |
|
||||
|
||||
The design's defence is already in place: the pool is drawn by vote weight (sortition), and vote weight is 30 days of blocks, which a prover ASIC does not have. A prover ASIC centralises the external job market, not the pool. What it would do to the pool is set the shard deadline: if the chain tightens the deadline toward ASIC times, home cards miss it. So the shard deadline must be a function of the measured fleet median (lane 7's rule), re-read each era, and the shard size must stay at a size a 12 GB card proves alone inside it (today 4.8 to 14.4 s). "Verified proving as the reward" (horizon-2026-10 item 1: the proof verified in consensus) is the piece that makes the pool unforgeable, and its activation is the decision owed.
|
||||
|
||||
Per tier: 8 GB (proves alone, never beside its miner; keeps its pool share by weight); 12 GB (the swing tier; the hand-off profile is the product); 16 GB and up (unconstrained); rig (proves beside mining on every card from 2028 at 1.5x); pool user (the pool operator's prover does it); Windows, Linux (the same); macOS (an M5 Max proves a shard, unmeasured against the rented table; Metal lane open); NVIDIA (every measurement is NVIDIA); AMD (no measured shard time; the 9070 XT has no SP1 GPU backend measured here, so the AMD tier proves on CPU or not at all until it is measured, which is the first owed number); Apple (as macOS).
|
||||
|
||||
### 5.3 Proof-system risk and the swap interface
|
||||
|
||||
| Event | Date | What a malicious prover could do | Source |
|
||||
|---|---|---|---|
|
||||
| SP1 v3.4.0 (LambdaClass, 3MI, Aligned) | disclosed 26 Jan 2025 | "generate valid SP1 proofs of incorrect execution of an arbitrary program": universal forgery, from two bugs plus a Plonky3 evaluation gap | LambdaClass blog; Blockworks |
|
||||
| RISC Zero zkVM 2.0.0 to 2.0.2 | 15 May 2025 | a missing constraint in the rv32im circuit let any 3-register instruction be proven wrong; on-chain verifiers stopped by estop | HackenProof |
|
||||
| SP1 Hypercube JALR | 20 May 2026 | a completeness bug (prover crash), not soundness; the EF's audit found only 51 of 62 opcodes fully proven, four load instructions proven against wrong specifications | zkevm.ethereum.foundation |
|
||||
|
||||
Three soundness-class events in 18 months across the two leading zkVMs. On Igneum today a soundness bug is a light-client problem (spec 10.1; finality-in-proof.md 4.4): every full node executes natively and vetoes a record whose statement differs. It becomes a chain failure the day any of three things is true: (1) proof verification in consensus pays a shard on the proof alone (the 0.3.16 switch) and a forged proof carries a correct statement, which earns the pool and nothing else; (2) finality is carried inside the proof (lane 7's 3.3) and a forged proof carries a forged weight table, which a full node still vetoes, so the failure is confined to proof-only clients; (3) the chain ever lets a proof replace execution for full nodes, which the design forbids. So the chain-failure case is (3), and the rule is: never. The swap interface needs: a pinned verifier key per proof system with a version in the record; two independent zkVMs accepted in parallel (the EF's own "multiple provers" lesson), with a record valid only if its system is on the active list; a 95 percent class-change signal to retire a system, and a stop switch (RISC Zero's estop pattern) that any 1/3 of weight can flip to "execution only" inside an hour. Per tier: nothing a miner does; a pool's prover must build for two systems; the cost is two guest builds and two verifier keys.
|
||||
|
||||
## 6. Light clients on phones, 2026 to 2036
|
||||
|
||||
### 6.1 The state of the art
|
||||
|
||||
| Client | What it verifies | Bytes and time | Source |
|
||||
|---|---|---|---|
|
||||
| Ethereum sync-committee clients (Helios, Kevlar) | 512-validator sync committee signatures; syncs in about 2 s, no storage; "lightweight enough to run on mobile" | about 25 KB per two days (24,576 bytes of keys plus the signature, header and branch) | a16z Helios; annotated spec |
|
||||
| Mina | a recursive SNARK of the whole chain | a 22 KB chain; "a few milliseconds of processing" | Mina docs via gate.com, iq.wiki |
|
||||
| Celestia Lumina | data-availability sampling in a browser or phone (Wasm, Rust) | random shares a block; bandwidth not published for 2026 (approximate: tens of MB a day) | celestiaorg/lumina; Eiger |
|
||||
| Bitcoin BIP-157/158 (Neutrino: Breez, Blixt) | compact block filters served by full nodes | headers and filters in under 5 minutes | Blixt; Lightning Labs |
|
||||
| StarkWare's Bitcoin header proof | every header since genesis in one STARK | 1 MB, under 100 ms on a phone | cryptotimes 11 Sep 2025 |
|
||||
| Zcash (Zashi/Zodl SDKs, lightwalletd; Tachyon-Lite) | shielded sync through servers | server-dependent (the anti-pattern) | Zcash forum, GitHub |
|
||||
| Kaspa wallets (Kaspium, kaspa-core) | none: wallets talk to public nodes over wRPC | n/a (approximate) | coincodex; Zelcore |
|
||||
| Phone verify times | Groth16 about 5 ms; a Keccak circuit's verify 0.12 s on an iPhone 15 Pro (Mopro); a 45 KB STARK in 16 ms (laptop, approximate); Groth16 proving 2.9 to 3.1 s on a Galaxy S25 and iPhone 17 Pro | Mopro benchmarks; provebench; shattered.io | cited, mixed devices |
|
||||
| WebGPU | in Chrome 113, Edge 113, Safari 18, Firefox 141 | n/a | Wikipedia WebGPU |
|
||||
|
||||
### 6.2 What Igneum's finality-in-proof needs from the phone
|
||||
|
||||
The client holds one proof and fetches the newest segment proof from any node (finality-in-proof.md 4.2); SP1's compressed verify is 0.032 s on a Mac core (bench-log 5 Oct), a Groth16 or Plonk wrapper is unmeasured. Model (a wallet updating every 10 minutes, 144 times a day, a phone big core at 3 W, LTE at about 1.5 J a MB, an 18 Wh battery):
|
||||
|
||||
| Form the wallet verifies | Bytes a day | CPU | Radio | Battery a day |
|
||||
|---|---|---|---|---|
|
||||
| Groth16 or Plonk wrapper (about 1 KB with public values) at about 10 ms on a phone (approximate, 3x the Mac core) | 0.14 MB | 4 J | under 1 J | 0.007 percent |
|
||||
| Compressed STARK at the EF's 300 KiB ceiling, about 100 ms on a phone | 42 MB | 43 J | 63 J | 0.16 percent |
|
||||
|
||||
Either is nothing. The choice is the EF's: no trusted setup and under 300 KiB, which is the STARK row, and by 2030 a WASM-SIMD or WebGPU verifier on a phone should put the STARK verify under 30 ms (approximate projection from the 294x STARK-verifier speedup reported in 2026 and the WebGPU adoption row). The 2026 answer is the Plonk wrapper (no trusted setup, small) for the phone and the compressed STARK for nodes. Bytes a day are set by update frequency, and a wallet that updates on open rather than on a timer is under 1 MB a day in either form.
|
||||
|
||||
"Every miner is also a light-client server" has prior art: Bitcoin full nodes serving BIP-157 filters under the NODE_COMPACT_FILTERS service bit (every full node that opts in serves every light client), and Ethereum's Portal Network (every node serves a slice; "without having to trust or put extra strain on full nodes"). The anti-pattern is Zcash's lightwalletd, a few servers everyone trusts. Recommendation: the node serves `igneum_getSegmentProofBytes` over p2p and a rate-limited HTTP path, on by default in Ember, so the serving set is the miner set; the phone client dials three miners and takes the longest valid chain of proofs. Per tier: no cost a miner notices (one proof of a few hundred KB served on request); the pool node serves for its members; Windows, Linux, macOS, every vendor the same.
|
||||
|
||||
## 7. Post-quantum signatures and a proof-of-work chain
|
||||
|
||||
### 7.1 The standards and sizes
|
||||
|
||||
| Scheme | Standard | Public key | Signature | Status | Source |
|
||||
|---|---|---|---|---|---|
|
||||
| ML-DSA-44 | FIPS 204 | 1,312 B | 2,420 B | final (Aug 2024) | Cloudflare 9 Jul 2026; FIPS 204 |
|
||||
| ML-DSA-65 | FIPS 204 | 1,952 B | 3,309 B | final | encryptionconsulting |
|
||||
| ML-DSA-87 | FIPS 204 | 2,592 B | 4,627 B | final | same |
|
||||
| SLH-DSA-SHA2-128s / 128f | FIPS 205 | 32 B | 7,856 B / 17,088 B | final | Cloudflare |
|
||||
| FN-DSA-512 (Falcon) | FIPS 206 | 897 B | 666 B | draft; final expected late 2026 to early 2027; floating-point signing is "difficult to implement securely" | encryptionconsulting; Cloudflare |
|
||||
| ML-KEM | FIPS 203 | n/a | n/a | final | NIST |
|
||||
| BLS12-381 (today) | n/a | 48 B | 48 B aggregate for 8,192 voters | quantum-broken by Shor on the curve's discrete log | ethereum.org PQ page |
|
||||
|
||||
Timelines: NIST IR 8547 deprecates ECDSA, EdDSA, RSA and EC Diffie-Hellman after 2030 and disallows them after 2035 (final version confirmed). The Global Risk Institute's 2025 survey (26 experts, report page dated 9 Mar 2026): a cryptographically relevant quantum computer "quite possible (28 to 49 percent)" within 10 years and "likely (51 to 70 percent)" within 15. Resource estimates: ECC-256 at 1,200 to 1,450 logical qubits and 70 to 90 M Toffoli (Google, 2026), about 500,000 physical qubits against about 1 M for RSA-2048 (Gidney, May 2025); 835 logical qubits with 2^30.6 Toffoli (Luo et al., arXiv 2607.13816, Jul 2026). Bitcoin: BIP-360 merged into the BIPs repository 11 Feb 2026 as a draft (P2MR, formerly P2QRH), a companion BIP for ML-DSA and SLH-DSA, a testnet implementation March 2026, no activation. Ethereum: a PQ team formed January 2026; BLS to be replaced by leanXMSS (hash-based) with leanVM aggregating "3,000 bytes" of signature against BLS's 96 by "250x"; core PQ infrastructure targeted "by approximately 2029"; EIP-8141 account abstraction for user migration.
|
||||
|
||||
### 7.2 What quantum does to Igneum
|
||||
|
||||
| Component | Break | Consequence | Size of the fix |
|
||||
|---|---|---|---|
|
||||
| BLS vote keys (in every header) | Shor on BLS12-381's G1 discrete log (a 255-bit subgroup over a 381-bit field; wider than secp256k1, so a later break by a year or two, approximate) | every vote key is public; a CRQC forges two thirds of weight and certifies any chain. Catastrophic | the migration below |
|
||||
| Hash-to-curve | none; it is a hash | nothing | nothing |
|
||||
| ECDSA accounts in the EVM | as Ethereum: exposed public keys after a first spend | user funds; the same exposure as Ethereum's "0.1 percent dormant" row, smaller | account abstraction with an ML-DSA verify precompile from genesis |
|
||||
| The VRF (aggregator pick) | if EC-based, forgeable; aggregation is not safety (every voter signs) | liveness nuisance | the same key succession |
|
||||
| The class-group VDF (epoch seed, era draw) | Shor computes the class-group order, which removes the sequentiality assumption of a Wesolowski-style VDF (approximate; from memory of the class-group VDF literature) | an attacker with a CRQC grinds the hourly program seed | a hash-chain fallback behind the same version byte; flagged, not sized |
|
||||
| SP1 (STARK core, Groth16/Plonk wrapper) | the STARK is hash-based and stands; the pairing wrapper falls | the on-chain and phone verifier moves to the compressed STARK | the wrapper choice of section 6 |
|
||||
|
||||
### 7.3 The cost in bytes, 8,192 voters, every voter signing every 30-second checkpoint (model)
|
||||
|
||||
| Scheme | Per checkpoint | Per day (2,880 checkpoints) | Averaged per block at 1 block/s | Key in the header |
|
||||
|---|---|---|---|---|
|
||||
| BLS aggregate (today) | 1.0 KB (48 B + a 1,024 B bitmap) | 2.9 MB | 34 B | 48 B |
|
||||
| ML-DSA-44 | 19,361 KB | 57.1 GB | 645 KB | 1,312 B |
|
||||
| ML-DSA-65 | 26,473 KB | 78.1 GB | 882 KB | 1,952 B |
|
||||
| FN-DSA-512 | 5,329 KB | 15.7 GB | 178 KB | 897 B |
|
||||
| SLH-DSA-128s | 62,849 KB | 185 GB | 2,095 KB | 32 B |
|
||||
| SLH-DSA-128f | 136,705 KB | 403 GB | 4,557 KB | 32 B |
|
||||
| ML-DSA-44 aggregated by a SNARK at the EF's 250x | 77 KB | 223 MB | 2.6 KB | 1,312 B, or a 32 B hash of it |
|
||||
|
||||
A naive ML-DSA swap costs 57 GB a day of votes against a block stream of about 100 MB a day (approximate), 500x. It is not shippable without aggregation. The aggregator already exists in the design (VRF picks 8 aggregators a checkpoint): the migration is "aggregators carry a STARK of N verified PQ signatures", which is Ethereum's leanVM shape, and the per-checkpoint cost falls to the order of 100 KB.
|
||||
|
||||
### 7.4 The plan to pre-commit at genesis
|
||||
|
||||
1. A `sig_scheme` version byte in the vote item and in the vote-key registration; genesis value 0 = BLS12-381.
|
||||
2. Key succession at registration: every vote key registers with a 32-byte hash of a successor public key (any scheme). A `succeed` item signed by the old key and the new key moves the 30-day weight to the successor without a reset. Miners generate the successor at first run; Ember stores it offline.
|
||||
3. A PQ verify precompile (ML-DSA-44 first, FN-DSA when FIPS 206 is final) in the EVM from genesis, so account abstraction can migrate users before 2030.
|
||||
4. The flip is a class change: 95 percent signal with a floor height, as the P2 mechanism; the aggregated-vote format ships first, the scheme flip second.
|
||||
5. The VDF carries the same version byte with a hash-chain fallback.
|
||||
6. The public sentence: "Igneum's vote keys and accounts can move to NIST post-quantum signatures by miner signal; the key succession is in every key from genesis."
|
||||
|
||||
Per tier at migration: a home miner on any card runs the updater and signs one `succeed` item from Ember; ML-DSA-44 signing is well under a millisecond on any CPU (approximate), every 30 s; the bandwidth cost is the aggregated form's, about 100 KB a checkpoint, which an 8 GB card's host handles; a rig's one key succeeds once; a pool's key is the operator's, and pool members do nothing; Windows, Linux, macOS, NVIDIA, AMD, Apple: nothing hardware-specific, since the signature runs on the CPU.
|
||||
|
||||
## 8. Regulation of mining and of coins with no issuer, 2026 to 2036
|
||||
|
||||
### 8.1 The texts
|
||||
|
||||
| Regime | What it says | Source |
|
||||
|---|---|---|
|
||||
| MiCA Article 4(3) | Title II (offers and white papers) does not apply where the crypto-asset is "automatically created as a reward for the maintenance of the distributed ledger or the validation of transactions" (point (b)); where there is no identifiable issuer "the obligation to produce a white paper does not apply to an issuer, as none exists"; a CASP that plays an active or promotional role may have to draw up one (Article 5 and the trading-platform duty under Title V) | Osborne Clarke (4 Oct 2023); Conventus Law; Squire Patton Boggs |
|
||||
| MiCA sustainability | Article 66 and Delegated Regulation 2025/422: CASPs publish the consensus mechanism's energy (kWh a year mandatory; more above 500,000 kWh); the Commission's Article 142 report may propose "minimum sustainability standards"; the 2026 review consultation (reply by 31 Aug 2026) asks only how appropriate the regime is (Q53) | the consultation PDF; carbon-ratings; Latham tracker |
|
||||
| MiCA transition | over on 1 Jul 2026; unlicensed service to EU clients is a breach | ESMA statement Apr 2026 |
|
||||
| UK | The Financial Services and Markets Act 2000 (Cryptoassets) Regulations 2026: regulated activities from 25 Oct 2027 (issuing qualifying stablecoins, safeguarding, operating a qualifying cryptoasset trading platform, dealing, arranging, arranging staking); gateway and savings window 30 Sep 2026 to 28 Feb 2027; a "qualifying cryptoasset" is the FCA perimeter term (the formal definition sits in the Regulations and was not retrievable here); mining and validating are not in the activity list found; the 2023 financial-promotions regime already covers promotions of qualifying cryptoassets to UK consumers | FCA policy statements page; Lewis Silkin 30 Sep 2026; Skadden Jul 2026 |
|
||||
| US | SEC Corporation Finance staff statement 20 Mar 2025: PoW mining, solo or pooled, is not an offer of securities (non-binding, facts and circumstances); CLARITY (CFTC over digital commodities) failed cloture 49 to 50 on 15 Sep 2026, a motion to reconsider preserved; GENIUS Act 2025 covers payment stablecoins; DAME 30 percent excise proposed three times, never enacted | Dechert; Orrick 2 Oct 2026; Cointelegraph |
|
||||
| FATF | Recommendation 16 revised June 2025; implementation by end-2030; unhosted wallets still treated differently by country | FATF best practices Jun 2025; Sumsub |
|
||||
| DIFC (Igneum Labs LTD's seat) | DFSA crypto-token regime amended 12 Jan 2026: firm-led suitability, no more Recognised Crypto Tokens list, controller approval at 30 and 50 percent; applies to firms providing financial services in crypto tokens; nothing on mining or software | Dechert 13 Jan 2026; Arabian Business |
|
||||
| Dubai outside DIFC | VARA rulebook v2.1 (2026): exchange services, disclosures, token issuance; a federal licensing decision Feb 2026 | TradingView/Coinpedia; Dechert |
|
||||
|
||||
### 8.2 What it means for each actor
|
||||
|
||||
| Actor | EU | UK | US | DIFC | FATF |
|
||||
|---|---|---|---|---|---|
|
||||
| A pool operator paying members | not a CASP for mining; a CASP if it holds members' coins or exchanges them (custody, exchange); the energy figure is the CASPs' duty, not the pool's | not an activity listed, unless it safeguards or deals; from 25 Oct 2027 a pool that holds balances looks like safeguarding | SEC: pooled mining not a security; FinCEN money-transmitter guidance on pools is the open point (approximate) | not a financial service unless it custodies | a pool that holds and sends on behalf of members is a VASP-shaped entity by end-2030 |
|
||||
| A home miner selling coins | a seller, not an offeror; taxed as income then gains (country rules) | the same; promotions rules do not reach a private sale | income at receipt (IRS), then gains | n/a | the exchange it sells on carries the travel rule |
|
||||
| The entity publishing the software (Igneum Labs LTD) | no issuer under 4(3); the white paper is nobody's duty; publishing the 2025/422 indicators voluntarily lets CASPs list | not an activity; public text must not be a financial promotion of a qualifying cryptoasset to UK consumers (the 2023 regime), which rules out "buy", "invest" and price talk | the SEC statement covers mining, not the publisher; CLARITY's "mature blockchain" test is the thing to watch if it passes | a software company in the DIFC with no financial service and no token sale sits outside the DFSA token regime on its face | n/a |
|
||||
| The testnet with no value | nothing; no asset | nothing | nothing | nothing | nothing |
|
||||
| The external proving market paid in dollars | a job is a service; settlement in IGN on-chain with no operator custody is not a CASP activity; an operator that converts dollars to IGN is an exchange | the same; dealing or arranging if an operator sits between | money transmission if an operator holds funds | a dollar-settled service is a financial service only if the entity holds or exchanges | the same |
|
||||
|
||||
### 8.3 What Igneum writes into its genesis and public text now (not legal advice; counsel is engaged)
|
||||
|
||||
1. No issuer, no offeror, no sale: every coin is created as a block reward (MiCA Article 4(3)(b) language, verbatim in the litepaper).
|
||||
2. The 2025/422 sustainability indicators (kWh a year from hash rate and measured uJ, intensity per transaction, mix by region when known) published by the project each era, so an EU CASP can list without asking.
|
||||
3. No financial promotion: no price, no "buy", no "invest", no return language anywhere the UK can read it; the earnings page shows hash and IGN, not sterling.
|
||||
4. The pool reference implementation never holds a member's balance: payouts are direct from the coinbase split (the pool is a coordinator, not a custodian), which keeps pools out of CASP, safeguarding and VASP shapes in every regime above.
|
||||
5. The job market is peer-to-peer settled on-chain; no operator account, no dollar leg in the protocol; the dollar price is a quote, the settlement is IGN.
|
||||
6. The software is published under an open licence by a company that holds no coins by right (no dev fund, already decided) and runs no service the chain depends on.
|
||||
7. The region prompt and the banned-region list in Ember (Russia's ten regions and Moscow from 15 Aug 2026, China) with the sentence "mining may be restricted where you are; you are responsible for checking".
|
||||
8. A genesis "no privileged key" statement: no key can mint, pause or upgrade (true by design; say it because the regulators' tests turn on it).
|
||||
|
||||
## 9. Ten-year scenario table
|
||||
|
||||
| Year | Scenario A: GPUs stay general and cheap | Scenario B: HBM-only high end, APU-only low end | Scenario C: proving ASICs win |
|
||||
|---|---|---|---|
|
||||
| 2028 | 48 GB flagship on GDDR7 (lane 7); used H100s at USD 7,500 to 10,500 flood rentals; hash at the rental equilibrium, home share falling with the rent curve. Igneum: the N ladder's first rung by signal; home miners inside 2.5x of the chip per joule; the chip pays at USD 100 M of cap. Alive. The parameter: N stepped by signal | HBM4 only on datacentre parts; consumer GDDR7 unchanged; APUs (Medusa Halo, M6) take the laptop tier. Igneum: nothing changes for the hash; the Apple and APU tiers grow. Alive. The parameter: the cache rung, in case an APU's LLC reaches 256 MB | First VPU or C1 boards ship at 10 to 50x per joule on MSM and NTT; the external job market goes to them within a year. Igneum: the 20 percent pool is sortition by weight, so home cards keep it; the dollar market is lost to ASICs. Alive, with the external income line (already "never the main income" by 2030, lane 7 3.11) gone sooner. The switch: the shard deadline as a function of the fleet median |
|
||||
| 2031 | GDDR7-Next; 64 GB flagship; rental at 0.002 to 0.004 per MH/s-hour; hash 3 to 6x today's at the same price. The chip bar is USD 1.4 B at N5. Home miners on retail power are marginal unless the heat mode and the price prompt keep the UK and EU tiers on in winter. Alive; the home share is 10 to 20 percent (approximate). The parameter: the weight-aged reward rule (lane 7 3.1) | A consumer HBM4-class halo card appears (unannounced today): 2 to 4x the read rate of a 2026 card; the home 2026 cards fall to 0.25 to 0.5x of a new card, the GPU cadence twice over. The chip's stack-level edge is the same as the card's, so the per-joule gap closes to about 1.5x. Alive; the installed base shifts to the new card. The parameter: the N bind-point re-measurement per generation | Prover ASICs at 100x; the EF's L1 zkEVM runs on them; SP1-class software moves to ASIC backends. Igneum: proving in consensus verified, home cards prove shards inside the deadline because the deadline is the fleet's; the pool's 20 percent stays with weight. Alive. The risk: if a soundness bug in the ASIC's backend lands, the stop switch of 5.3 is the defence |
|
||||
| 2036 | 96 GB cards; rent at 0.0003 to 0.0013; hash 10 to 35x; the emission at the 1 percent tail (86 M IGN a year). Security is 1 percent of market cap a year (tail-emission.md). Igneum lives if the cap is over about USD 50 M (a 24-hour attack at 0.032 percent of cap is USD 16,000 against a rental market that can supply it); dies below that only in the sense every PoW chain does: cheap to attack, nothing to steal. The parameter: the tail | HBM everywhere above the laptop; the f = 1 chip and the halo card are the same memory; the ASIC question is closed by parity and the ladder's N is the remaining difference. Alive. The parameter: N at the verifier's 10 ms ceiling | Every proof is an ASIC proof; the pool pays miners for shards an ASIC proves 100x cheaper; miners buy ASIC prover boards the way they buy cards (USD 500 to 2,000 boards, approximate guess). Igneum: proving is a second card, not a second income. Alive. The parameter: the shard deadline rule |
|
||||
|
||||
Where Igneum dies, honestly:
|
||||
|
||||
| Death | Scenario | What would be needed to survive it |
|
||||
|---|---|---|
|
||||
| The stored-dataset chip at N5 is built at a USD 100 M cap in year 1 and holds over a third of hash by year 2 | A, 2028, if the N ladder is not at genesis or the signal never steps it | the N ladder at genesis with its first rung live; the weight-aged reward; the honest sentence that the chip's bar is USD 100 M, so the cap passes it fast or not at all |
|
||||
| Idle datacentre hash sets the rent and the home tier leaves; weight concentrates in three hosting firms; a hosting failure is a 30-day finality pause | A, 2031 | the LEAVE item (shipped), the reward rule that pays aged keys, the heat mode and price prompt that keep winter home miners on, and a public "weight by hosting provider" chart so the concentration is seen |
|
||||
| A CRQC in 2033 to 2036 (28 to 49 percent inside ten years, GRI) before the key succession has flipped | any, 2033+ | the genesis key succession and the aggregated-vote format shipped by 2030, the flip signalled the year NIST deprecates (2030), not the year a machine appears |
|
||||
| A soundness bug in the one proof system while the pool pays on the proof alone | C, any year | two zkVMs on the active list and the 1/3-weight stop switch |
|
||||
| The cap never passes USD 20 M: the tail's 1 percent is USD 200,000 a year of security, a day's rental | all, any year | nothing in the protocol; it is the market's verdict, and the design should say so rather than promise a floor |
|
||||
|
||||
## 10. Verdict table
|
||||
|
||||
| Axis | Finding | Source | Per-tier consequence | Recommendation |
|
||||
|---|---|---|---|---|
|
||||
| Memory roadmap | tRC flat for about 20 years; HBM4 doubles channels a stack (2026); HBM4E adds pins and a custom base die (2027 to 2028); no consumer HBM, no GDDR8 before 2029 to 2031 | JEDEC, SK hynix roadmap, Lee et al. | a 2026 card keeps its read rate against a 2028 card; 8 and 12 GB leave the installed base by 2030, not the hash | nothing at genesis; re-measure the N rungs per generation (text, a measurement duty) |
|
||||
| The cache | consumer LLC 96 to 128 MB; datacentre 256 MB today | chip-model-v3, MI300X (approximate) | an LLC at the cache size gives that card's owners a 2 to 3x shortcut | a cache-size rung on the signal ladder (genesis parameter) |
|
||||
| The dataset | 2 GiB to 16 GiB over 28 years is under every card and never binds before year 12 | algorithm.md 5.6 | the prover footprint binds first | nothing; fix the public card-lifetime sentence (text) |
|
||||
| The chip's bar | pays at USD 17 M of cap bare, USD 100 M with the N5 shadow core in years 1 to 2, 2.3x higher every two years with the glide; never needs N2 or A16 | model, siliconanalysts mask costs | the 9070 XT tier is displaced first, Apple never per joule, the 5090 inside 2.5x | the N ladder at genesis (parameter); the p* sentence in the threat model (text) |
|
||||
| C-HBM4E | the memory controller moves under the stack from 2027 to 2028 | TSMC/GUC via Tom's | the chip's dollars per MH/s fall under USD 2.8 by 2028 (approximate) | disagreement with lane 7 row 2.3 recorded; no new parameter, N covers it |
|
||||
| Used-GPU flood | 15 to 20 M datacentre GPUs installed by end-2026; residual 25 to 35 percent at 60 months; an H100 mines at 0.65x to 3x a 5090, unmeasured | TechInsights, Omdia, mercatus | 1 percent of a retired fleet is 2 to 35x the equilibrium hash | measure an H100 and an A100 this month (measurement); the reward rule (parameter, lane 7 3.1) |
|
||||
| Rental curve | 0.0117 to 0.0003 to 0.0013 per MH/s-hour by 2036 | model on cloudzero's H100 history | attack dollars unchanged at equilibrium; home share falls 10 to 35x | re-price 51-percent.md from a measured rental yearly (text); the cost-vs-rent line on the earnings page (software) |
|
||||
| Energy | UK 26.3 p, EU 29 c average, US 17.7 c; heat credit is a third off in the heating season; ERCOT pays industrial curtailment, not homes | Ofgem, Eurostat, EIA, ABC13 | retail-power tiers (UK, DE) are 5 to 9x the industrial price even with heat credited | heat mode, curtailment input, per-region price prompt (software); 2025/422 indicators published (text) |
|
||||
| Mining rules | Norway (new sites), Sweden (tax), Russia (regions and Moscow), China (illegal), Kazakhstan and Paraguay (licensed, tariffed); US SEC: mining is not a securities offer | the table in 4.4 | a home miner's risk is regional | the region prompt and banned list in Ember (software) |
|
||||
| Proving cost | USD 1.69 to about 0.005 a block in 20 months; four teams at sub-10 s; EF's 2026 metric is kWh a proof; no prover ASIC has shipped a block benchmark | ethproofs, EF, Cysic | a 12 GB card proves alone under 10 s at every rate; beside its miner only at 3x a year | the shard deadline as a function of the measured fleet median (parameter); two zkVMs and the 1/3 stop switch (switch) |
|
||||
| Proof-system risk | three soundness-class events in 18 months across SP1 and RISC Zero | LambdaClass, HackenProof, EF | none for miners today; a chain failure only if a proof ever replaces execution for full nodes | never let it (text in spec 10.1); the active-list and stop switch (switch) |
|
||||
| Light clients | 25 KB per two days (Ethereum), 22 KB (Mina), a 1 MB STARK in under 100 ms on a phone (StarkWare); a verifying Igneum wallet costs under 0.2 percent of a day's battery | a16z, Mina, cryptotimes, model | nothing a miner notices; every miner serves proofs | serve the segment proof from every node by default (software); the Plonk wrapper for phones, the STARK for nodes (parameter) |
|
||||
| Post-quantum | ML-DSA-44 2,420 B signatures; 57 GB a day of naive votes at 8,192 voters, 223 MB with 250x aggregation; CRQC 28 to 49 percent within ten years; NIST deprecates 2030 | FIPS 204, Cloudflare, GRI, NIST IR 8547, model | a miner signs one succession item at migration; nothing hardware-specific | the sig_scheme byte, the key succession, the PQ precompile, the VDF fallback at genesis (parameters); the aggregated-vote format by 2030 (switch) |
|
||||
| Regulation of a no-issuer coin | MiCA 4(3)(b) exempts block-reward assets; UK regulates platforms, dealing, custody, staking from 25 Oct 2027, not mining; SEC mining statement; CLARITY stalled; FATF by 2030 | the texts in 8.1 | a pool must not custody; a miner selling is a seller; the publisher is nobody's issuer | the eight-item list of 8.3 (text); the non-custodial pool reference (software) |
|
||||
| Scenarios | alive in every 2028 and 2031 cell with one parameter each; dies on an unstepped N ladder, on concentration after the home tier leaves, on a late PQ flip, on a single proof system, or on a cap under about USD 20 M | section 9 | the home tiers are the ones each death removes first | the five "needed to survive" rows of section 9 |
|
||||
|
||||
## 11. Three headline findings for the coordinator
|
||||
|
||||
1. A stored-dataset chip with the class v4 shadow core pays for itself at about USD 100 M of market cap in Igneum's first two years and about USD 700 M in years 5 to 6 (30 percent share, USD 30 M project, model), and it never needs a node below 28 nm plus N5, so the N ladder at genesis is the only lever and C-HBM4E (2027 to 2028) moves the chip's controller under the memory and cuts its dollar cost further.
|
||||
2. Ten years of rental deflation (USD 0.0117 to 0.0003 to 0.0013 per MH/s-hour by 2036) leaves every 51-percent dollar figure unchanged at the equilibrium and cuts the home card's share of the subsidy 10 to 35x; the day an idle datacentre card on 5 c power sets the rent, a UK home miner at 26.3 p loses 7x on electricity, and the heat mode buys back a third, not the gap.
|
||||
3. A post-quantum vote at 8,192 voters costs 57 GB a day in naive ML-DSA-44 (19.4 MB a checkpoint) and about 223 MB a day with 250x SNARK aggregation, against a 28 to 49 percent chance of a cryptographically relevant quantum computer inside ten years (GRI 2025) and NIST deprecation after 2030, so the key-succession item, the scheme byte and the aggregated-vote format belong at genesis and the flip belongs in 2030.
|
||||
|
||||
File: `/Users/joshm/Projects/igneum-wt-mission/docs/analysis/mission/future.md`.
|
||||
|
||||
## Sources (accessed 7 October 2026 unless dated otherwise)
|
||||
|
||||
Lane and repository inputs: `docs/analysis/horizon/frontier.md` (lane 7), `docs/analysis/horizon/algorithm.md` (lane 2), `docs/analysis/chip-model-v3.md`, `docs/analysis/51-percent.md`, `docs/bench-log.md` ("Rental cost of hash, 6 October 2026"), `horizon-2026-10.md`, `finality-in-proof.md`, `tail-emission.md`, `vote-or-burn.md`, `prover-tiers-real-cards.md`.
|
||||
|
||||
Memory and GPUs:
|
||||
- TrendForce, NVIDIA fuels HBM4 race, 9 Jan 2026: https://www.trendforce.com/news/2026/01/09/news-nvidia-demand-fuels-hbm4-race-12-layer-ramps-16-layer-push-by-sk-hynix-samsung-and-micron/
|
||||
- EE Times, The state of HBM4 at CES 2026: https://www.eetimes.com/the-state-of-hbm4-chronicled-at-ces-2026/
|
||||
- Astute Group, SK hynix 62 percent of HBM: https://www.astutegroup.com/news/general/sk-hynix-holds-62-of-hbm-micron-overtakes-samsung-2026-battle-pivots-to-hbm4/
|
||||
- siliconanalysts HBM pricing: https://siliconanalysts.com/tools/hbm-analysis
|
||||
- TrendForce, Micron HBM4E 2027 to 2028, 23 Dec 2024: https://www.trendforce.com/news/2024/12/23/news-micron-plans-hbm4-mass-production-in-2026-customized-hbm4e-to-launch-in-2027-2028/
|
||||
- Tom's Hardware, TSMC and GUC HBM4E and C-HBM4E: https://www.tomshardware.com/pc-components/dram/hbm-undergoes-major-architectural-shakeup-as-tsmc-and-guc-detail-hbm4-hbm4e-and-c-hbm4e-3nm-base-dies-to-enable-2-5x-performance-boost-with-speeds-of-up-to-12-8gt-s-by-2027
|
||||
- TechTimes, Samsung HBM4E samples, 30 May 2026: https://www.techtimes.com/articles/317400/20260530/samsung-ships-industry-first-hbm4e-samples-36-tb-s-bandwidth-beats-sk-hynix-six-months.htm
|
||||
- Tom's Hardware, SK hynix roadmap to 2031: https://www.tomshardware.com/pc-components/dram/sk-hynix-reveals-dram-development-roadmap-through-2031-ddr6-gddr8-lpddr6-and-3d-dram-incoming
|
||||
- TechSpot, GDDR7 successor after 2028: https://www.techspot.com/news/110151-sk-hynix-expects-launch-ddr6-gddr7-successor-after.html
|
||||
- TweakTown, RTX 60 Rubin GR20x: https://www.tweaktown.com/news/109619/next-gen-geforce-rtx-60-gpus-rumored-to-use-rubin-gr20x-family-gearing-up-for-2027-release/index.html
|
||||
- wccftech, RTX 60 2028 launch rumour: https://wccftech.com/nvidia-rtx-60-2028-launch-rumor/
|
||||
- wccftech, 4 GB and 6 GB GDDR7: https://wccftech.com/4-gb-and-6-gb-gddr7-memory-chips-will-be-rolled-out-in-the-next-two-years/
|
||||
- TechPowerUp, UDNA 96 CUs: https://www.techpowerup.com/339101/amds-upcoming-udna-rdna-5-gpu-could-feature-96-cus-and-384-bit-memory-bus
|
||||
- TweakTown, RDNA 5 mid-2027: https://www.tweaktown.com/news/112289/amds-rdna-5-radeon-gpus-will-launch-in-mid-2027-per-new-leak/index.html
|
||||
- Tom's Hardware, AMD GDDR7 driver support: https://www.tomshardware.com/pc-components/gpus/amd-begins-to-add-gddr7-support-to-its-linux-gpu-drivers-changes-could-herald-use-of-advanced-memory-standard-with-next-gen-radeon-gpus
|
||||
- VideoCardz, Medusa Halo LPDDR6: https://videocardz.com/newz/amd-ryzen-max-500-medusa-halo-rumored-to-support-lpddr6-memory
|
||||
- Apple, M5 Pro and M5 Max, 3 Mar 2026: https://www.apple.com/newsroom/2026/03/apple-debuts-m5-pro-and-m5-max-to-supercharge-the-most-demanding-pro-workflows/
|
||||
- Apple, Mac Studio M5 Max and M5 Ultra, Aug 2026: https://www.apple.com/newsroom/2026/08/apple-introduces-new-mac-studio-with-m5-max-and-m5-ultra/
|
||||
- JEDEC LPDDR6 press release: https://www.jedec.org/news/pressreleases/jedec%C2%AE-releases-new-lpddr6-standard-enhance-mobile-and-ai-memory-performance
|
||||
- JEDEC DDR5 JESD79-5D: https://www.jedec.org/standards-documents/docs/jesd79-5d
|
||||
- Lee et al., Reducing DRAM latency at low cost (latency stagnation): https://arxiv.org/pdf/1604.08041
|
||||
- Chang et al., Flexible-latency DRAM: https://arxiv.org/pdf/1805.03154
|
||||
- Wikipedia, Feynman microarchitecture: https://en.wikipedia.org/wiki/Feynman_(microarchitecture)
|
||||
|
||||
Chip costs:
|
||||
- siliconanalysts wafer and mask pricing (Sep 2026): https://siliconanalysts.com/data/wafer-pricing
|
||||
- siliconanalysts tapeout cost guide, 1 Mar 2026: https://siliconanalysts.com/analysis/fabless-startup-tapeout-cost-guide
|
||||
- siliconanalysts TSMC 3 nm cost: https://siliconanalysts.com/guide/tsmc-3nm-cost
|
||||
- SemiWiki, TSMC A14: https://semiwiki.com/forum/threads/tsmc-a14-at-2026-dec-iedm.25920/
|
||||
|
||||
GPU supply and rental:
|
||||
- HPCwire, TechInsights 3.76 M datacentre GPUs 2023, 10 Jun 2024: https://www.hpcwire.com/2024/06/10/nvidia-shipped-3-76-million-data-center-gpus-in-2023-according-to-study/
|
||||
- Omdia, AI data centre chip market, Aug 2025: https://omdia.tech.informa.com/pr/2025/aug/ai-data-center-chip-market-to-hit-286bn-growth-likely-peaking-as-custom-asics-gain-ground
|
||||
- Tom's Hardware, Blackwell cabinet forecasts halved: https://www.tomshardware.com/tech-industry/artificial-intelligence/analysts-halve-nvidia-gb200-blackwell-shipment-forecasts-for-2025-prediction-contrasts-ai-boom
|
||||
- Jon Peddie Research, Q2 2026 AIB shipments: https://www.jonpeddie.com/news/q226-pc-graphics-aib-shipments-increased-10-from-last-quarter-to-12-million-units/
|
||||
- mercatus-ai, H100 resale value (23 Jun 2026): https://www.mercatus-ai.com/blog/h100-resale-value
|
||||
- intuitionlabs, used AI GPU prices 2026: https://intuitionlabs.ai/articles/used-ai-gpu-prices-resale-trends
|
||||
- cloudzero, H100 price 2026: https://www.cloudzero.com/blog/h100-gpu-cost/
|
||||
- spheron, GPU cloud pricing 2026: https://www.spheron.network/blog/gpu-cloud-pricing-comparison-2026/
|
||||
|
||||
Energy, heat, mining rules:
|
||||
- Ofgem price cap: https://www.ofgem.gov.uk/your-energy-supply/your-energy-bill/energy-price-cap-unit-rates-and-standing-charges
|
||||
- Eurostat, household electricity prices H2 2025, 5 May 2026: https://ec.europa.eu/eurostat/web/products-eurostat-news/w/ddn-20260505-1
|
||||
- EIA, US electricity prices: https://www.eia.gov/todayinenergy/detail.php?id=65284
|
||||
- EIA AEO2025 via The Energy Coop: https://theenergy.coop/blog/unpacking-2025-aeo/
|
||||
- Cornwall Insight via Solar Power Portal, 10 Jan 2024: https://www.solarpowerportal.co.uk/energy-policy/cornwall-insight-forecasts-lower-gb-power-prices-on-the-long-road-to-truly-affordable-energy-
|
||||
- spark.money, mining profitability by country: https://www.spark.money/tools/bitcoin-mining-profitability-by-country
|
||||
- Hashrate Index, Paraguay 2026 (4 May 2026): https://hashrateindex.com/blog/the-state-of-bitcoin-mining-in-paraguay-2026-2/
|
||||
- miningboard, bitcoin heaters 2026: https://miningboard.com/guides/bitcoin-heaters
|
||||
- techbuzz on Wired's Heatbit review, 5 Apr 2026: https://www.techbuzz.ai/articles/heatbit-s-bitcoin-mining-heater-fails-the-math-test
|
||||
- Qarnot (French Wikipedia): https://fr.wikipedia.org/wiki/Qarnot
|
||||
- ABC13, ERCOT paid Riot USD 31.7 M: https://abc13.com/post/ercot-texas-power-grid-riot-bitcoin-miners/13753300/
|
||||
- Riot June 2025 update: https://www.globenewswire.com/news-release/2025/07/03/3109856/0/en/Riot-Announces-June-2025-Production-and-Operations-Updates.html
|
||||
- CoinDesk, Norway ban, 23 Jun 2025: https://www.coindesk.com/tech/2025/06/23/norway-plans-ban-on-new-crypto-mining-data-centers-to-preserve-power
|
||||
- CoinDesk, Sweden tax, 14 Apr 2023: https://www.coindesk.com/policy/2023/04/14/sweden-drives-final-nail-into-its-bitcoin-mining-industry-with-tax-hike
|
||||
- crypto.news, Sweden back-tax: https://crypto.news/sweden-orders-six-crypto-firms-to-pay-56m-in-additional-taxes/
|
||||
- TASS, Moscow mining ban: https://tass.com/politics/2152151
|
||||
- Cryptopolitan, Russia regions: https://www.cryptopolitan.com/russia-regional-crypto-mining-ban-2025/
|
||||
- Caspian News, Kazakhstan lifts restrictions, 17 Nov 2025: https://caspiannews.com/news-detail/kazakhstan-lifts-restrictions-on-cryptocurrency-mining-trading-2025-11-17-36/
|
||||
- KuCoin, Kazakhstan strategic mining from 1 Aug 2026: https://www.kucoin.com/news/flash/kazakhstan-to-enforce-strategic-crypto-mining-rules-from-august-1
|
||||
- Lightspark, China 2026: https://www.lightspark.com/knowledge/is-crypto-legal-in-china
|
||||
- egw.news, China provinces (uncorroborated): https://egw.news/crypto/news/30440/china-officially-brings-back-mining-sichuan-inner--NnyU95QrT
|
||||
- ainfp, Iran mining 2026: https://ainfp.org/mining-crypto-in-iran-legal-status-restrictions-risks
|
||||
- Dechert, SEC PoW mining statement, Mar 2025: https://www.dechert.com/knowledge/onpoint/2025/3/sec-staff-issues-statement-on-proof-of-work-crypto-mining-activi.html
|
||||
- Fortune, EIA survey backlash, 1 Mar 2024: https://fortune.com/crypto/2024/03/01/department-energy-survey-bitcoin-miners-legal-backlash/
|
||||
- Cointelegraph, DAME tax revived: https://cointelegraph.com/news/crypto-mining-tax-united-states-budget
|
||||
- sazmining, US state mining laws 2026: https://www.sazmining.com/blog/us-crypto-mining-rules-review
|
||||
|
||||
Proving:
|
||||
- EF, Realtime proving, 10 Jul 2025: https://blog.ethereum.org/2025/07/10/realtime-proving
|
||||
- Ethproofs 2025 review, 6 Dec 2025: https://hackmd.io/@willcorcoran/S1A840ZMZg
|
||||
- Succinct, SP1 Hypercube on mainnet: https://blog.succinct.xyz/sp1-hypercube-is-now-live-on-mainnet/
|
||||
- bex.co, Cysic Venus, 17 Apr 2026: https://bex.co/blog/2026/04/17/cysic-venus-multi-gpu-zk-proving-real-time-verification-bottleneck
|
||||
- bex.co, Airbender, 30 Jan 2026: https://bex.co/blog/2026/01/30/zksync-airbender-fastest-risc-v-zkvm-ethereum-proving
|
||||
- GitHub comparative analysis, Sep 2026: https://github.com/Ricosworks1/blockchain-payment-flow-analysis/releases/tag/comparative-analysis-ethereum-zk-proving-race-sept-2026
|
||||
- cryptobriefing, Lattice Jolt, 9 Sep 2026: https://cryptobriefing.com/lattice-jolt-post-quantum-zkvm/
|
||||
- RISC Zero, R0VM 2.0: https://risczero.com/blog/introducing-R0VM-2.0
|
||||
- h33.ai, ZK proof companies 2026: https://h33.ai/blog/zk-companies/
|
||||
- Cysic ZK ASIC docs: https://docs.cysic.xyz/hardware-products/zk-asic-products/
|
||||
- Ingonyama and Accseal: https://www.ingonyama.com/oldblogs/partnership-announcement-accseal-x-ingonyama
|
||||
- LambdaClass, SP1 exploit disclosure, 26 Jan 2025: https://blog.lambdaclass.com/responsible-disclosure-of-an-exploit-in-succincts-sp1-zkvm-found-in-partnership-with-3mi-labs-and-aligned-which-arises-from-the-interaction-of-two-distinct-security-vulnerabilities/
|
||||
- Blockworks, SP1 bug: https://blockworks.co/news/succinct-sp1-bug
|
||||
- HackenProof, RISC Zero missing constraint, May 2025: https://hackenproof.com/blog/for-hackers/risc-zero-zkvm-missing-constraint-vulnerability
|
||||
- EF, On formal verification and a bug in SP1 Hypercube, 20 May 2026: https://zkevm.ethereum.foundation/blog/sp1-fv
|
||||
|
||||
Light clients:
|
||||
- a16z Helios: https://github.com/a16z/helios and https://a16zcrypto.com/posts/article/building-helios-ethereum-light-client/
|
||||
- Ethereum annotated spec, sync protocol: https://github.com/ethereum/annotated-spec/blob/master/altair/sync-protocol.md
|
||||
- Portal Network: https://ethportal.net/overview
|
||||
- Mina 22 KB: https://www.gate.com/learn/articles/what-is-mina-protocol/4814
|
||||
- Celestia Lumina: https://github.com/celestiaorg/lumina
|
||||
- Lightning Labs Neutrino: https://github.com/lightninglabs/neutrino
|
||||
- Blixt features: https://blixtwallet.github.io/features
|
||||
- StarkWare 1 MB Bitcoin verifier, 11 Sep 2025: https://www.cryptotimes.io/2025/09/11/starkware-unveils-1mb-bitcoin-verifier-for-mobile-devices/
|
||||
- Mopro benchmarks: https://zkmopro.org/docs/0.2/performance/ and https://hackmd.io/@vivi432/mopro-benchmark
|
||||
- provebench: https://github.com/agnij-dutta/provebench
|
||||
- shattered.io, STARK 45 KB verify: https://shattered.io/bulletproofs-vs-zk-starks-2026/
|
||||
- Wikipedia WebGPU: https://en.wikipedia.org/wiki/WebGPU
|
||||
- Zcash Tachyon-Lite grant: https://forum.zcashcommunity.com/t/grant-proposal-tachyon-lite-light-client-sdk-and-sync-proof-server-for-zcash/55671
|
||||
|
||||
Post-quantum:
|
||||
- Cloudflare, ML-DSA will have to do, 9 Jul 2026: https://blog.cloudflare.com/ml-dsa-will-have-to-do/
|
||||
- encryptionconsulting, FN-DSA FIPS 206: https://www.encryptionconsulting.com/education-center/fn-dsa-fips-206/
|
||||
- encryptionconsulting, NIST IR 8547 deadlines: https://www.encryptionconsulting.com/nist-ir-8547-2030-2035-action-plan/
|
||||
- NIST PQC standardisation: https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization
|
||||
- Global Risk Institute, Quantum Threat Timeline Report 2025: https://globalriskinstitute.org/publication/quantum-threat-timeline-report-2025b/
|
||||
- postquantum.com, Google ECDLP resources: https://postquantum.com/security-pqc/google-quantum-bitcoin-ecdlp/
|
||||
- postquantum.com, 835 logical qubits (arXiv 2607.13816): https://postquantum.com/security-pqc/ecc-secp256k1-835-logical-qubits/
|
||||
- arXiv, Brace for impact (ECDLP challenges): https://arxiv.org/pdf/2508.14011
|
||||
- ethereum.org, post-quantum cryptography: https://ethereum.org/roadmap/security/quantum-resistance/
|
||||
- The Quantum Insider, EF PQ team, 26 Jan 2026: https://thequantuminsider.com/2026/01/26/ethereum-foundation-elevates-post-quantum-security-to-top-strategic-priority/
|
||||
- The Quantum Insider, BIP-360 testnet, 20 Mar 2026: https://thequantuminsider.com/2026/03/20/btq-technologies-implements-bip-360-quantum-resistant-bitcoin-transactions-testnet/
|
||||
- qsha256, BIP-360 and BIP-361: https://qsha256.com/blog/bitcoin-pq-migration
|
||||
|
||||
Regulation:
|
||||
- Osborne Clarke, MiCAR white papers and Bitcoin, 4 Oct 2023: https://www.osborneclarke.com/insights/what-are-eus-white-paper-requirements-micar-and-do-they-apply-bitcoin
|
||||
- Conventus Law, crypto-assets without an identifiable issuer: https://conventuslaw.com/report/crypto-assets-without-an-identifiable-issuer-regulatory-reality-two-years-post-micar-application-date/
|
||||
- Latham, MiCA white paper and sustainability disclosures: https://www.lw.com/en/markets-in-crypto-assets-regulation-tracker/mica-white-paper-sustainability-disclosures
|
||||
- carbon-ratings, MiCA sustainability indicator methods: https://carbon-ratings.com/dl/whitepaper-mica-methods-2024
|
||||
- European Commission, 2026 MiCA review targeted consultation (reply by 31 Aug 2026): https://finance.ec.europa.eu/document/download/62be7015-f066-4fac-b74e-71bacdbcc9f5_en?filename=2026-mica-review-targeted-consultation-document_en.pdf
|
||||
- ESMA, end of MiCA transitional periods, Apr 2026: https://www.esma.europa.eu/sites/default/files/2026-04/ESMA75-113276571-1679_Statement_on_the_end_of_transitional_periods_under_MiCA.pdf
|
||||
- FCA, cryptoasset regime policy statements: https://www.fca.org.uk/publications/policy-statements/cryptoasset-regime
|
||||
- Lewis Silkin, UK draft Regulations and perimeter guidance, 30 Sep 2026: https://www.lewissilkin.com/insights/2026/09/30/uk-cryptoasset-regime-draft-regulations-and-fca-perimeter-guidance-provide-furth-102o3vf
|
||||
- Skadden, FCA finalises core rules, Jul 2026: https://www.skadden.com/insights/publications/2026/07/fca-finalises-core-rules-for-the-uk-cryptoasset-regime
|
||||
- Orrick, CLARITY stalls, 2 Oct 2026: https://www.orrick.com/en/Insights/2026/10/The-CLARITY-Act-Stalls-in-the-Senate-Whats-Next-for-Digital-Asset-Regulation
|
||||
- FATF, travel rule supervision best practices, Jun 2025: https://www.fatf-gafi.org/content/dam/fatf-gafi/recommendations/Best-Practices-Travel-Rule-Supervision.pdf
|
||||
- Sumsub, FATF Recommendation 16 revision: https://sumsub.com/media/spotlight/same-rule-same-problems-will-fatf-travel-rule-fix-the-divide/
|
||||
- Dechert, DFSA crypto token regime, 13 Jan 2026: https://www.dechert.com/knowledge/onpoint/2026/1/dubai-updates-crypto-token-regulatory-framework.html
|
||||
- TradingView/Coinpedia, UAE and VARA 2026: https://www.tradingview.com/news/coinpedia:681009ff8094b:0-crypto-regulations-in-uae-dubai-in-2026/
|
||||
528
docs/analysis/mission/invent.md
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
# The last mission, lane 4: what can be invented, judged hard
|
||||
|
||||
7 October 2026, morning UK. Lane 4 of the final research programme ("the last mission"), worktree `igneum-wt-mission`. The question: given everything Igneum already has (the horizon one page, the frontier lane's sixteen ideas, the new-PoW lane's three schemes, class v5, finality in the proof, work-stake, the tail, vote-or-burn, the ladder, the pool design, the light client, the phone app), what is the NEXT genuinely new thing a GPU chain could do that none has. Every candidate here was checked against prior art by name and year, run through its own game theory with a model this lane ran (`invent_model.py`, section 0), reviewed in the Monero or Kaspa developer's voice, priced in agent hours with a first gate, and given its per-tier consequence and four persona verdicts. Nothing here touches the devnet. No em dashes. Figures from memory say approximate; figures from a source name it.
|
||||
|
||||
the project lead's rulings are applied as a filter before any idea is scored: miners are the security always; no stake and no coin bond; no holder penalised; no device bounty; no fixed-height activations; no calendar dates; no reliance on another chain; no dev fund; no privacy; nothing that becomes a token sale; the lottery and the proving stay separate; emission carries security with no end date. An idea that needs one of these is marked dead in one line.
|
||||
|
||||
## 0. Progress, method and the four voices
|
||||
|
||||
| Time (UK) | State |
|
||||
|---|---|
|
||||
| 08:05 | Lane started; read CLAUDE.md, the horizon one page (sections 1 to 3), frontier.md (sections 0 and 3, all sixteen), new-pow.md (3, 4, 6, 7), class-v5-stored-state.md, finality-in-proof.md, work-stake.md, tail-emission.md (one page), vote-or-burn.md, latency-ladder.md, 51-percent.md, pool.md, spec 09, spec 10, phone-app.md, the four persona files, bench-log (rental cost, warp verify, aggregation, 5090 rows) |
|
||||
| 08:35 | Prior art checked by WebFetch on primary pages (the session's WebSearch budget was already spent; every page that answered is in the sources list; four pages 404'd and those items are labelled approximate) |
|
||||
| 08:51 | `invent_model.py` written and run in the lane's scratch (`mission-invent/out.md`); every table below marked "model" is printed by it |
|
||||
| 08:59 | This file written (528 lines, 0 em dashes, not staged); final message to the coordinator |
|
||||
|
||||
**Method.** Three passes per candidate. (1) Prior art: the paper, project or repository that did it, with the year, and the one sentence of what differs. (2) Game theory: who gains from cheating, the attack, its cost, the bound; a short numeric model actually run. (3) The hostile review in the Monero or Kaspa developer's voice, then the prototype cost in agent hours, the first measurable gate, the per-tier consequence (home miner 8 / 12 / 16 / 24 to 32 GB, rig, pool user; Windows, Linux, macOS; NVIDIA, AMD, Apple) and four persona verdicts: do now, prototype, watch, never.
|
||||
|
||||
**The four voices.** The cryptographer, the consensus engineer and the miner (the miner-community lead) are the persona files in `.claude/agents/`. The fourth, the economist, is defined here from the economy lane's method (`docs/analysis/horizon/economy-and-utility.md`, `tail-emission.md`): every number is priced in rented hash at the measured USD 0.0117 per MH/s-hour (bench-log, "Rental cost of hash", 6 October 2026); an IGN price is an input never a prediction, shown at USD 0.005, 0.02 and 0.10; fees are never assumed to be the security budget; a mechanism that moves income is judged by who pays, who is paid, and whether the payer would rather leave; the per-tier consequence is stated before anyone asks.
|
||||
|
||||
**Price basis used throughout.** Subsidy 100 IGN a block at one block a second (the tail-emission recommendation; today's code pays 31.688). Rented hash USD 11.7 per GH/s-hour. CPU verify of one 32-lane unit: 0.441 ms class v3 steady (bench-log line 170), 5.06 ms class v4 cold on the box's core (horizon L2), 1.35 ms per pool share check in isolation (pool.md section 5); the gate is 10 ms. Header 400 B working figure (spec 10.9). Snapshot 114.8 MB (horizon rank 22). Groth16 proof 260 B (SP1 docs), public values with the finality extension 494 B (finality-in-proof section 1).
|
||||
|
||||
**What the measurement budget did not allow.** Nothing in this lane needed the box or a GPU pod to reach a verdict; where a first gate needs one, the gate names the command and the expected number and the final message lists it for the coordinator.
|
||||
|
||||
---
|
||||
|
||||
## 1. Hashing useful to the chain itself, without reopening the useful-work trap
|
||||
|
||||
The trap, restated from new-pow section 3.1 and frontier 3.10: a lottery needs a sampleable puzzle with a cheap verifier; any coupling of the puzzle to a scarce skill hands the lottery to the best at that skill (Aleo). The four variants below avoid the trap by keeping the hash kernel byte for byte as shipped and changing only what a miner must HOLD or must have DONE to build the dataset or to be paid. That is class v5's shape (new-pow scheme C: hash rate 63.083 against 63.088 MH/s on the 4090, build +1.4 ms, verifier +0.11 to 0.21 ms per unit), so the question for each is only: what does it add to class v5 that is measurable, and does the addition survive its own game.
|
||||
|
||||
### 1.1 What class v5 already delivers, so nothing below re-invents it
|
||||
|
||||
| Property | Delivered by class v5 | Where |
|
||||
|---|---|---|
|
||||
| Every mining operation holds the day's execution state | yes, by construction: `leaf(t) = D[t mod n]` keys every item; a stateless hasher is wrong on every item (the known-failed case) | class-v5 section 3, 4 |
|
||||
| Every block names 128 random state leaves per lane any full node can open against `R_d` | yes; the opening RPC is new-pow rank 6 (4 hours, not built) | new-pow 3.3, 7 |
|
||||
| The miner must know the chain to within the epoch lead | yes, already since class v3: the program is the 10-minute VDF of a certified checkpoint 20 minutes before the epoch (spec 04 4.3); a miner without the last hour of chain has the wrong program and every hash is wrong | spec 04 |
|
||||
| The f = 0 recompute chip must hold the state | yes | new-pow 6 |
|
||||
| The pool caveat | stated: a pool ships `D` once a day (6 KB today, 1 to 2 GiB on a used chain) | class-v5 section 3 |
|
||||
|
||||
### 1.2 (a) Proof of following: the day's leaves include the last N blocks
|
||||
|
||||
**The idea.** Widen class v5 so that the dataset derives from the state AND from the chain's recent blocks: the day's leaf array gains the hashes or receipts of the last N chain blocks before the cut, so a miner proves it relayed or held recent blocks, not only the state.
|
||||
|
||||
**Prior art.** Verthash (Vertcoin, January 2021, approximate: the vertcoin.org page 404'd this morning): a 1.2 GB dataset built from the chain's own block headers, random reads per hash. Ethash (2015): dataset from the epoch seed. Class v5 (this project, 7 October 2026): dataset from the execution state. Permacoin (Miller, Juels, Shi, Parno, Katz, IEEE S&P 2014): proof of retrievability of a dealer's file. The combination "state plus recent headers" is new in form; Verthash is the nearest in substance.
|
||||
|
||||
**Mechanism.**
|
||||
|
||||
| Item | Rule |
|
||||
|---|---|
|
||||
| Extra leaves | after the state records, one record per chain block in `(daa(C_d) - N, daa(C_d)]`: `0x04 ‖ block_hash ‖ state_root ‖ receipts_root` |
|
||||
| Keyed by | `R_d` as every other leaf; the day's cut `C_d` is one hour before the day (class-v5 section 2) |
|
||||
| What it forces | a builder must hold the last N headers and receipt roots before the cut |
|
||||
| What class v5 forces already | the whole state after `C_d`, which commits to every one of those blocks through `R_d` |
|
||||
|
||||
**Game theory.** Nobody gains from cheating because there is nothing to cheat: the state root already commits to the chain. The only question is what a miner must HOLD. Under class v5 a miner holds `D` (the state sample); under 1.2 it also holds N header records it can fetch from any peer in one round trip. Model (section A of `invent_model.py`, the outsourcing row): a key with nothing answers a fetch in about 108 ms over a 100 ms link. The addition forces no holding that a pool's daily delivery does not already satisfy, and the "following" it proves is the following the epoch seed proves every hour at zero cost.
|
||||
|
||||
**What is measurable.** Nothing new per block. The receipts are a function of the state transition every executing node already computed; the day's cut already requires the chain to within an hour; the epoch seed requires it to within 80 minutes. The one measurable thing would be a per-block dataset change, and a per-block build is 32 ms on a 4090 (class-v5 section 2) against a 1,000 ms block: a 3.2 percent hash loss on every card every second for a property the program swap gives every hour for free.
|
||||
|
||||
**Hostile review (Kaspa voice).** "You have a dataset keyed by a state root that is itself a function of every block in the chain's past. Adding the last N block hashes as leaves adds information the root already carries. If you want 'following', the program swap is your following: a miner that is an hour behind has the wrong kernel. Do not add bytes to a build that already proves the thing."
|
||||
|
||||
**Cost and gate.** 0 hours: not built. If someone insists: the gate is the fast-time harness showing a stateful miner with stale headers is refused, which the state root already does.
|
||||
|
||||
**Per tier.** No change for any card, rig or pool user; the verifier's RAM unchanged.
|
||||
|
||||
**Verdicts.** Cryptographer: never (the root commits to the chain; redundant). Consensus engineer: never (bytes for nothing). The miner: never (nothing to see). Economist: never (no income moves). Verdict: never, with the sentence "proof of following is the epoch seed plus class v5" written into class-v5's litepaper paragraph.
|
||||
|
||||
### 1.3 (b) Proof of serving: a reward component for serving headers, snapshots and proofs
|
||||
|
||||
**The idea.** A slice of the producer's 80 points is paid only to keys that served data to peers in the window, verified by challenge-response sampled into blocks.
|
||||
|
||||
**Prior art.** Filecoin WindowPoSt (2020; spec.filecoin.io: 48 deadlines of 30 minutes a day, randomness from a beacon 20 epochs before the deadline, fee debt on a missed proof); Arweave SPoRA (version 2.4, February 2021, approximate: the docs page 404'd; mining reward requires random access to stored chunks); Sia storage proofs (2015, approximate) and Storj audits (2018, approximate); Celestia data availability sampling (2023, approximate); EigenDA (2024, approximate); Ethereum PeerDAS (EIP-7594, 2024: custody by node id, cell KZG proofs, and the EIP states no reward or penalty for serving); Kaspa archival nodes (a flag on kaspad, unrewarded; the README fetched this morning does not document it, approximate). What differs here: the data served is the chain's own state, the challenger is the next block producer and the randomness is the chain's, and the reward is a slice of the block subsidy to a miner key, not a storage market.
|
||||
|
||||
**Mechanism.**
|
||||
|
||||
| Item | Rule |
|
||||
|---|---|
|
||||
| Challenge | block B's producer draws `c` keys from the weight table at the last certified checkpoint in B's past, by weight, seeded by B's prehash; for each, 8 leaf indices of the day's `D` |
|
||||
| Answer | the challenged key signs `(B, leaves, openings against R_d)` and any producer carries it within `T` blocks |
|
||||
| Failure | no answer carried in `T` blocks after B |
|
||||
| Reward | x of the 80 producer points conditional: a key with an unanswered challenge in the window is paid 80 - x on its next blocks until it answers one |
|
||||
| Bytes | 8 leaves x (64 + 25 x 32) + 96 B signature = 6.8 KB per answer; 1 challenge per block = 0.61 GB a day on every node (model, table A2) |
|
||||
|
||||
**Game theory (model, section A).**
|
||||
|
||||
| Attack | Cost | Bound |
|
||||
|---|---|---|
|
||||
| Serve yourself from your own nodes | zero; the challenged key answers its own challenge from the state it already holds to mine under class v5 | the proof reduces to "I hold what class v5 already makes me hold" |
|
||||
| Outsource the answer to a peer that holds the state | one fetch, about 108 ms on a 100 ms link; any deadline over one block (1,000 ms) admits it | the proof becomes "someone with the state answered"; Filecoin's answer is sealing (replicas slower to fetch than to read), which costs hours of CPU per sector and does not fit a daily dataset |
|
||||
| Producer griefing: refuse to carry answers | an answer gossips to every producer; withholding for T blocks needs a majority of producers for T seconds | the same bound finality lives with |
|
||||
| Sybil the draw | the draw is by weight; a dust key is never drawn; splitting a key splits its draws and its loss | nothing to gain |
|
||||
| Loss to a key that never serves at x = 8 | 0.1 percent key: 691 IGN a day (USD 3.5 at 0.005); 10 percent key: 69,120 IGN a day | the price of not running a node, which a miner under class v5 must run anyway |
|
||||
|
||||
**Load per key.** At 10,000 keys and one challenge per block a key's share of challenges is its weight share: 8.64 a day for a mean key, 0.06 MB of upload a day, 0.006 kbit/s. At 128 leaves per challenge and 8 per block, 7.6 MB a day. Every home connection carries it (table A1). The cost is on the chain, not the key: 0.61 to 76.5 GB a day of answers on every node depending on the two parameters.
|
||||
|
||||
**Hostile review (Monero voice).** "You pay a miner for proving it holds the thing your own lottery forces it to hold. The challenge adds 0.6 GB a day to every node to learn nothing, and the fetch bound makes it 'someone served', which is what gossip already guarantees. Filecoin pays for storage because storage is the product; your product is blocks. The one honest version is unpaid: a light-client spot check of availability, which is your rank 6 RPC."
|
||||
|
||||
**Cost and gate.** 0 hours as a reward; 4 hours as new-pow rank 6 (the opening RPC, unpaid), whose gate is 128 openings verifying against `R_d` on the fast-time network.
|
||||
|
||||
**Per tier.** As a reward: every tier pays bytes (0.61 GB a day of chain) to prove what class v5 proves. As the unpaid RPC: a node serves 102 KB per request on demand; a light client gains a daily availability spot check; no card changes.
|
||||
|
||||
**Verdicts.** Cryptographer: never as a reward, do the RPC (the outsourcing bound makes the proof vacuous). Consensus engineer: never (0.61 to 76 GB a day for no new property). The miner: never (a line item nobody understands). Economist: never (it moves x points for a service already compelled). Verdict: never as protocol; new-pow rank 6 stands.
|
||||
|
||||
### 1.4 (c) Proof of propagation: signed receipts from peers in the next block
|
||||
|
||||
**The idea.** A block carries receipts, signed by peers' vote keys, that they received the previous block within t seconds; the producer is paid a slice for fast propagation.
|
||||
|
||||
**Prior art.** Bitcoin's Relay Network and FIBRE (Matt Corallo, 2016; bitcoinfibre.org: UDP, forward error correction, compact blocks; no protocol payment to relays). Kaspa's relay is the ordinary p2p flow with no receipts (approximate). "Proof of relay" papers: this lane found none that shipped; the arXiv search for "proof of latency" returned five unrelated papers (section 7.11). GHOSTDAG itself: a slow block is red and its subsidy goes to its merger (spec 02 2.5), which is the propagation reward the DAG already pays.
|
||||
|
||||
**Mechanism and why it dies.**
|
||||
|
||||
| Item | Problem |
|
||||
|---|---|
|
||||
| The time field | no consensus clock finer than Kaspa's timestamp window (a header at most 10 s ahead of the clock, spec 02; the DAG tolerance 132 s in frontier 3.1); a receipt's "within t seconds" is the signer's word |
|
||||
| Sybil receipts | peers are keys; weight-drawn receipts cost the attacker nothing because its own keys sign its own receipts |
|
||||
| Bytes | 8 receipts x 136 B = 1,088 B per block, 94 MB a day; 32 receipts 376 MB a day (model, section H) |
|
||||
| What the DAG already does | at 1 block/s on Devnet 2 run B the red rate was under 2 percent from minute six (bench-log, "Block rate on Devnet 2"); a block that propagates slowly is red and its subsidy moves to its merger: the propagation reward exists and is measured in blue blocks, which are also the vote weight |
|
||||
|
||||
**Hostile review (Kaspa voice).** "GHOSTDAG is a proof of propagation. Blue means it arrived in time; red means it did not; the k parameter is the clock. Receipts signed by the people who benefit from them prove nothing and cost 94 MB a day."
|
||||
|
||||
**Verdicts.** All four: never. Cost 0.
|
||||
|
||||
### 1.5 (d) Proof of state availability with block-derived challenges and the producer's own next block
|
||||
|
||||
**The idea.** As 1.3 but the challenge is issued by the producer's own next block (so no trusted party), answered on demand, and the reward slice is paid only to keys that answered.
|
||||
|
||||
This is 1.3 with the challenger named. The trap the brief names ("who issues challenges without a trusted party") is answered correctly by block-derived randomness and the producer's next block, and the answer does not rescue the idea: the outsourcing bound (a fetch in 108 ms) and the self-serving bound (the key answers from the state class v5 makes it hold) are unchanged. The one new element, "the producer's own next block" as the carrier, makes the producer the judge of its own challenge and needs a second producer to carry the answer for fairness, which is 1.3's `T` blocks.
|
||||
|
||||
**Verdicts.** All four: never as a reward; the unpaid availability spot check (new-pow rank 6) is the whole of what survives. Cost 4 hours for the RPC.
|
||||
|
||||
### 1.6 Section 1 in one table
|
||||
|
||||
| Variant | Prior art | New? | Survives its game? | Cost | Verdict |
|
||||
|---|---|---|---|---|---|
|
||||
| (a) proof of following | Verthash 2021; class v5 2026 | in form only | yes, because it does nothing | 0 | never: the epoch seed and the state root already prove following |
|
||||
| (b) proof of serving | Filecoin PoSt 2020; Arweave SPoRA 2021; PeerDAS 2024 (unrewarded) | the reward to a miner key is new | no: outsourced in 108 ms; self-answered under class v5 | 0 (4 for the unpaid RPC) | never as a reward |
|
||||
| (c) proof of propagation | FIBRE 2016 (unpaid); GHOSTDAG's reds | no | no: time is unverifiable; receipts are Sybil-free | 0 | never |
|
||||
| (d) state availability by the producer's next block | same as (b) | the challenger rule is new | no, same bounds as (b) | 4 (RPC) | never as a reward |
|
||||
|
||||
---
|
||||
|
||||
## 2. A block reward that pays for availability of the chain's state
|
||||
|
||||
**The exact split proposal, as asked.** Of the 80 producer points, x are conditional on the key having served state in the window, verified as in 1.5; the rest unconditional. The candidate x: 2, 4 or 8 (8 would mirror the signing bonus of vote-or-burn, which pays 8 of the 80 for signing).
|
||||
|
||||
**The attack: serve yourself.** The challenged key answers from the state it holds; the challenge is drawn by weight, so the key's own weight is the only thing that decides how often it is asked. A key that runs a node (every class v5 miner) passes every challenge at zero marginal cost. A key that runs no node and proxies to a pool passes every challenge at one fetch. Nobody fails except a key whose node is down, which is the signing bonus's case already (vote-or-burn section 1: the honest-silence classes), so the conditional slice is a second liveness bonus on the same event.
|
||||
|
||||
**The Sybil bound.** Vote-key weight as the challenge draw: a dust key is never drawn, a split key is drawn in proportion, so splitting gains nothing. Correct, and it also means the mechanism measures the keys that already mine, who already hold the state.
|
||||
|
||||
**Per-tier cost (model, table A1).** At 10,000 keys, one challenge per block, 8 leaves: a mean key uploads 0.06 MB a day; at 8 per block and 128 leaves, 7.6 MB a day, 0.7 kbit/s. Any home connection on any OS carries it. The chain pays 0.61 to 76.5 GB a day of answer bytes on every node for the two parameter corners.
|
||||
|
||||
**What it costs a key that never serves (model, table A3).** At x = 8: 0.01 percent key 69 IGN a day, 0.1 percent 691, 1 percent 6,912, 10 percent 69,120 (USD 0.3 to 346 at 0.005). The same shape as the signing bonus and for the same population.
|
||||
|
||||
**Hostile review (Monero voice).** "Two liveness bonuses on one event is one bonus with worse accounting. Your signing bonus already pays 8 points for 'my node is up and following'; this pays x more for 'my node is up and holds the state', which under class v5 is the same node. Merge them or drop this."
|
||||
|
||||
**The economist.** No payer wants it: the slice comes from the same producer share, so it is a transfer from keys whose nodes are down to keys whose nodes are up, which the signing bonus already does; the chain pays gigabytes a day for the second accounting.
|
||||
|
||||
**Verdicts.** Cryptographer: never (vacuous under the outsourcing bound). Consensus engineer: never (bytes). The miner: never (a second deduction line is read as a second fine; vote-or-burn's 93 to 97 percent respect for one bonus at genesis would not survive two). Economist: never. Cost 0.
|
||||
|
||||
---
|
||||
|
||||
## 3. Mining that is also a light-client service
|
||||
|
||||
**The idea.** Every miner's node serves finality-in-proof public values (494 B) and certificate bitmaps to phones; a reward slice or a fee market pays for it.
|
||||
|
||||
**Prior art.** Ethereum Portal Network (2021 to 2026; ethportal.net: history, state and beacon networks; clients Trin, Nimbus Portal (Fluffy), Ultralight, Shisui; the site states no reward for nodes). Helios (a16z, 2022, approximate for the year; github: weak-subjectivity checkpoints as the root of trust, sync-committee verification). Mina's snarkers (2021, approximate): a fee market for SNARK work inside the protocol. Celestia light nodes and bridges (2023, approximate). What Igneum has that none of these had: the serving node is a miner with a funded key, and the object served (494 B of public values plus a 260 B Groth16 proof, once the wrapper lands) is tiny and self-certifying.
|
||||
|
||||
**Whether it is a protocol item.** No. The object is public values a node already holds; serving it is one HTTP or wss endpoint (spec 10.7's read-only table); the phone app already reads nodes it pins (phone-app section 5). A fee market for 786 bytes is a transaction per fetch at 0.0051 IGN (frontier 3.13's transfer floor), which no phone will pay per refresh and no node needs. A reward slice is 1.3's reward with a different payload and the same vacuity.
|
||||
|
||||
**What it is: an Ember feature.** Ember in verify mode is frontier 3.8 (do now, 20 hours). The light-client serving side is a line in the node's RPC: `igneum_getLatestWrappedProof`, served to any client, rate-limited per IP, with the phone app pinning the user's own node by its card (phone-app section 5) or the seed list. Bytes per day per phone: 786 B per refresh, 2,880 refreshes a day if it polls every checkpoint = 2.3 MB (spec 10.5's phase-two figure, 2.30 MB, matches).
|
||||
|
||||
**Game theory.** A node that lies serves a proof that does not verify, which the phone refuses; a node that withholds is replaced by the next pinned node. No payment, so no cheating for pay. The one attack is topology: a phone pinned to one node is eclipsed by that node, which spec 10.6's N of M seed agreement bounds.
|
||||
|
||||
**Hostile review (Kaspa voice).** "Serving 786 bytes is not a service you price. It is a feature of a node. Put it in the app."
|
||||
|
||||
**Cost and gate.** 4 hours (the RPC and the rate limit) on top of frontier 3.8's 20; gate: a phone on cellular shows "locked, voter set verified in the proof" from a miner's Ember node with no other node asked, bytes counted on the Verify screen.
|
||||
|
||||
**Per tier.** Every miner's Ember serves phones by default behind a per-IP limit (a home upload of 786 B per request is nothing); a holder's phone gets its proof from any miner; a pool user's member process is the same Ember; no card or OS difference.
|
||||
|
||||
**Verdicts.** Cryptographer: do now (as the Ember line). Consensus engineer: do now, not a protocol item. The miner: do now ("my node serves my phone" is a sentence miners like). Economist: do now, unpaid; a fee market here would price a service below its transaction cost.
|
||||
|
||||
---
|
||||
|
||||
## 4. A decentralised pool in the protocol
|
||||
|
||||
### 4.1 The design, as asked
|
||||
|
||||
| Item | Rule |
|
||||
|---|---|
|
||||
| A share | a block header at a low difficulty, signed by the miner's own payout key, referencing the round `r` |
|
||||
| The round | the window of blocks between two chain-block boundaries, e.g. 600 chain blocks (the record window of spec 7.8) |
|
||||
| Carriage | any block carries shares it has seen; a share is valid if its header's PoW passes the share target and its parents are in the carrier's past |
|
||||
| Payout | a coinbase rule every node computes: the producer share of blocks in round r is split by share weight (`2^-s`) over the shares carried in round r's blocks, PPLNS style, minus nothing (no operator, no fee) |
|
||||
| Variance reduction | a miner is paid by its shares whether or not it found a block |
|
||||
|
||||
### 4.2 Prior art
|
||||
|
||||
| Name | Year | What it did | What differs |
|
||||
|---|---|---|---|
|
||||
| P2Pool (Bitcoin) | 2011 (Forrest Voight, approximate) | a sidechain of shares at a 10-s target; payouts in the coinbase of found blocks; no operator | the shares were off the main chain; nodes did not verify them |
|
||||
| FruitChains | Pass and Shi, 2016 (eprint 2016/916) | "fruits" as low-difficulty PoW objects carried in blocks, rewarded fairly: any honest set with phi of the hash gets at least (1 - delta) phi of the reward | the exact in-protocol shape asked for here, published nine years ago; never shipped on a major chain |
|
||||
| SmartPool | Luu, Velner, Teutsch, Saxena, 2017 (eprint 2017/019) | shares committed by Merkle root to an Ethereum contract, verified by sampling; miners paid 0.6 percent of block rewards in transaction fees | a contract, not a coinbase rule; the operator is the contract |
|
||||
| Monero P2Pool | SChernykh, 2021 (github SChernykh/p2pool) | a 10-s share sidechain merge-mined with Monero, PPLNS over 2,160 shares (6 hours), no operator, payouts as coinbase outputs | off the main chain; Monero nodes do not verify shares |
|
||||
| Stratum V2 job negotiation | Braiins, 2019 onward (approximate) | the miner builds its own template; the pool must pay shares on it | an operator remains; the vote and template are the miner's (spec 09 already takes this) |
|
||||
| Ocean (Bitcoin) | 2023 (approximate) | an operator pool with on-chain payouts per block (TIDES) | an operator remains |
|
||||
| Braidpool | 2021 onward (github braidpool/braidpool: in development, not production) | a DAG ("braid") of shares; miners build their own blocks; constant-size payouts | not shipped |
|
||||
| Kaspa "SPECTRE shares" | none found | | Kaspa pools are Stratum bridges with operators (approximate) |
|
||||
|
||||
So the in-protocol pool is FruitChains (2016) with Igneum's payout key and round. It is not new.
|
||||
|
||||
### 4.3 The bytes and the verifier (model, section B)
|
||||
|
||||
| Miners | Share interval | Shares per block | Bytes per block (compact 169 B) | Per day on every node | Verifier cores at 1.35 / 5.06 / 10 ms per share |
|
||||
|---|---|---|---|---|---|
|
||||
| 1,000 | 10 s | 100 | 16.5 KB | 1.5 GB | 0.14 / 0.51 / 1.0 |
|
||||
| 10,000 | 10 s | 1,000 | 165 KB | 14.6 GB | 1.35 / 5.06 / 10.0 |
|
||||
| 100,000 | 10 s | 10,000 | 1,650 KB | 146 GB | 13.5 / 50.6 / 100 |
|
||||
| 100,000 | 100 s | 1,000 | 165 KB | 14.6 GB | 1.35 / 5.06 / 10.0 |
|
||||
| 100,000 | 1,000 s | 100 | 16.5 KB | 1.5 GB | 0.14 / 0.51 / 1.0 |
|
||||
|
||||
The full-header form (496 B) is 2.9x the bytes. The verifier at the class v4 cold figure (5.06 ms) needs 50 cores per node at 100,000 miners and a 10-s share interval, which no home node has; at a 1,000-s interval it fits one core and 1.5 GB a day, but then the variance reduction is the thing being bought and it has gone:
|
||||
|
||||
| Income source | Paying events per day | CV of a day's income | CV of a month |
|
||||
|---|---|---|---|
|
||||
| solo 17 MH/s at 1 TH/s | 1.5 | 82.5 percent | 15.1 percent |
|
||||
| shares at 1 per 1,000 s | 86 | 10.8 percent | 2.0 percent |
|
||||
| shares at 1 per 100 s | 864 | 3.4 percent | 0.6 percent |
|
||||
| shares at 1 per 10 s | 8,640 | 1.1 percent | 0.2 percent |
|
||||
|
||||
At 100,000 miners the chain can afford one share per 1,000 s per miner (1.5 GB a day, one core), which gives a small card a 10.8 percent daily CV: a real improvement on 82.5 percent, at the cost of 1.5 GB a day on every node including every phone-class light client that would want to verify the coinbase. At 10,000 miners and 100 s the same bytes buy 3.4 percent.
|
||||
|
||||
### 4.4 Does the vote key and sortition machinery give a round for free?
|
||||
|
||||
Yes for the ROUND, no for the SHARES. W2 already counts blue blocks per key over 30 days, and the shard sortition already draws by that count (spec 7.2). A "round" and a per-key tally exist in every node. What does not exist is a sub-block unit of work, and that is the whole cost: every share is a PoW object every node verifies. The free version is to pay by BLOCKS over a window, which the protocol already does (every block pays its producer), so the free "pool" is the chain itself, and its variance is the solo row above.
|
||||
|
||||
### 4.5 The attacks
|
||||
|
||||
| Attack | Effect | Bound |
|
||||
|---|---|---|
|
||||
| Share withholding (find a block, publish only shares) | in an operator pool the withholder is paid shares and the pool loses the block; here the round's payout IS the blocks found, so a withheld block shrinks everyone's payout including the withholder's own share of it: the withholder loses `(1 - its share) x 80` points and gains nothing | self-defeating; FruitChains' fairness bound is the formal version |
|
||||
| Share stuffing | every share is PoW at the share target, so a fake share costs its hash; stuffing is mining | none needed |
|
||||
| Share grinding on the round boundary | a share's round is fixed by its parents; a share straddling a boundary is a DAG event nodes already order | none needed |
|
||||
| A majority refusing to carry others' shares | it earns the majority a larger split of the round; this is red-flooding by another name, 26 to 28 percent of honest income at 51 percent (51-percent.md section 1) | the same bound as today's reds |
|
||||
| Verifier DoS | a flood of invalid shares costs a node 5 ms each before rejection | the per-peer rate limits of the p2p layer; the ban score |
|
||||
|
||||
### 4.6 The honest verdict against docs/plans/pool.md
|
||||
|
||||
The shipped pool v0 (5 October 2026, rebased 6 October) already removes the two things an in-protocol pool is for: the operator holds no key and no vote (members sign their own votes through their own verifiers, spec 9.7), and the share rule is fixed by spec 9.8 so the member keeps its own ledger. The measured operator cost is 1.35 ms per share check (4,800 to 22,700 members per core) and 1 percent fee by default. What the operator still controls: transaction choice for mode A members who do not check (mode C fixes it), and the payout ledger's honesty (the member's own ledger plus the chain's blocks under its key bound it).
|
||||
|
||||
The finished form is therefore the pool design plus one addition the brief names: **a verifiable payout contract**, which is SmartPool (2017) on Igneum's EVM: the operator posts the round's share Merkle root and the payout list; any member can prove under-payment with its own share log against the root; the contract pays from the pool address. Cost 16 hours (contract 6, operator posting 4, member proof and the Ember line 6). Gate: a member whose share is dropped from the root proves it on the fast-time network and is paid; an honest round costs the operator one transaction. Bytes on chain: 32 B per round per pool. Verifier cost on nodes: nothing per share.
|
||||
|
||||
**Hostile review (Monero voice).** "P2Pool exists because we did not want shares on the main chain; your numbers say why: 14.6 to 146 GB a day. Our P2Pool is a sidechain nobody but miners runs. Build that if you want no operator; it costs your chain nothing." Answer: a Monero-style P2Pool sidechain for Igneum is an app a pool operator could ship (the share chain's only chain-side object is the coinbase payout list), and it is the same code as the pool v0 member minus the server; the lane recommends the contract first because it keeps every existing pool (HiveOS, WhatToMine listings) and makes them verifiable.
|
||||
|
||||
**Per tier.** Home miner on any card: nothing changes in the worker; under the contract a member can prove under-payment from its own log. Rig: one key per rig as today. Pool user: the pool's round is public. Node operator: 32 B per round per pool instead of 1.5 to 146 GB a day. Light clients: unchanged.
|
||||
|
||||
**Verdicts.** Cryptographer: never in protocol (FruitChains is the prior art and the bytes are the reason it never shipped); prototype the contract. Consensus engineer: never in protocol (50 cores per node at 100,000 miners on a 10-s share); the contract 16 hours. The miner: never in protocol (a chain that grows 14.6 GB a day from shares is the first thing a reviewer attacks); the contract is what listings want. Economist: the operator's 1 percent is already below SmartPool's 0.6 percent plus gas (approximate); the contract makes the 1 percent honest. Verdict: never in protocol; prototype the verifiable payout contract, 16 hours.
|
||||
|
||||
---
|
||||
|
||||
## 5. Heat as product
|
||||
|
||||
**The idea.** Ember's "heat mode" targets a room temperature or a schedule: the card mines when the room wants heat and idles when it does not; the chain does nothing.
|
||||
|
||||
**Prior art.** Heatbit (2022 launch, approximate; heatbit.com this morning lists Bitair at 25 W, 1.2 TH/s, USD 179 to 249, and names earlier Maxi and Trio models), 21energy (Austria; Ofen 3 heaters EUR 1,658 to 3,325, "7 to 10 kW" central-heating models coming, 21energy.com), Qarnot (France, founded 2010, approximate, the Wikipedia page 404'd; QRad computing heater 2013, approximate; sells HPC and heats buildings), MintGreen in North Vancouver (2021, approximate), the Finnish and French district-heat trials (approximate; no page fetched). Every one is an ASIC or HPC box sold as a heater. None is a GPU chain's own client with a thermostat. Ember's version is new as a client feature and nothing else.
|
||||
|
||||
**Numbers (model, section C).** A 300 W card delivers 300 W of heat at the card's electricity price. The competition is a gas boiler at 90 percent or a heat pump at COP 3.
|
||||
|
||||
| Region | Card per hour | Gas boiler, same 300 W | Heat pump COP 3 | Resistive electric | Card must earn per hour to match boiler / heat pump |
|
||||
|---|---|---|---|---|---|
|
||||
| UK (Ofgem, 1 Oct to 31 Dec 2026: 26.32 p electricity, 7.97 p gas) | 7.90 p | 2.66 p | 2.63 p | 7.90 p | 5.24 p / 5.26 p |
|
||||
| EU (Eurostat H2 2025: EUR 0.2896; gas EUR 0.11 approximate) | 8.69 c | 3.67 c | 2.90 c | 8.69 c | 5.02 c / 5.79 c |
|
||||
| US (EIA July 2026: 18.31 c; gas 5 c approximate) | 5.49 c | 1.67 c | 1.83 c | 5.49 c | 3.83 c / 3.66 c |
|
||||
|
||||
Against a resistive heater (a bedroom panel, a US baseboard) the mining heat is free and every IGN earned is profit. Against a boiler or a heat pump the card must earn about 5.3 p an hour in the UK.
|
||||
|
||||
| Network hash | IGN per hour, 100 MH/s card at 100 IGN a block | at USD 0.005 | at 0.02 | at 0.10 |
|
||||
|---|---|---|---|---|
|
||||
| 100 GH/s | 360 | USD 1.80 | 7.20 | 36.00 |
|
||||
| 1 TH/s | 36 | 0.18 | 0.72 | 3.60 |
|
||||
| 10 TH/s | 3.6 | 0.018 | 0.072 | 0.36 |
|
||||
|
||||
So at 1 TH/s and USD 0.02 a 5090-class card earns 72 US cents (about 55 p) an hour and heats for 7.9 p: a heater that pays. At 10 TH/s and USD 0.005 it earns 1.8 cents and the heat costs 5.3 p more than gas: a heater that costs 3.9 p an hour net, which is still under the resistive panel it replaces in a room without a radiator. The honest sentence for the app: "In heat mode your card is an electric heater that also earns; whether it beats your boiler depends on the network and the price, shown live."
|
||||
|
||||
**Game theory.** None on the chain: a heat-mode miner is a miner with a schedule. One effect to name: seasonal hash. If heat mode becomes a large share of hash, winter hash rises and summer hash falls in the northern hemisphere; the DAA absorbs it, and the 30-day weight window means a summer departure of heat miners is a slow slide, not a departure event (the 10 percent per hour rule is not touched by a thermostat that ramps over weeks).
|
||||
|
||||
**Hostile review (Monero voice).** "A thermostat in a miner is a feature every miner GUI has had since 2014 (approximate: temperature targets in miner software). Call it heat mode if it sells; it changes nothing about the chain." Correct.
|
||||
|
||||
**Cost and gate.** App feature, 6 hours: a target-temperature input, the schedule, a sensor source (the OS's, or the card's own temperature as a proxy with a stated error), the live "earns X, heats Y, your boiler would cost Z" line from the tariff the user enters (phone-app 4.1 already has the tariff field). Gate: a room sensor loop on PC 1 holds a set temperature within 1 degree over four hours while the hash rate follows the duty cycle, logged.
|
||||
|
||||
**Per tier.** An 8 GB card at 150 W is a 150 W heater (the small-room case); a 5090 at 326 to 575 W (the ladder page's TGP range) is a large-room heater; a rig is a space heater that should not be in a bedroom; a pool user's duty cycle shows as a share-rate pattern the pool sees; macOS: the M5 Max at low watts is a poor heater; Windows and Linux alike.
|
||||
|
||||
**Verdicts.** Cryptographer: do now (no protocol content). Consensus engineer: do now, 6 hours. The miner: do now (the one feature a home miner in a cold flat asks for first; the number line must show when it loses to gas). Economist: do now, with the tariff line mandatory so nobody reads "free heat".
|
||||
|
||||
---
|
||||
|
||||
## 6. Phone-verifiable everything
|
||||
|
||||
**What remains after finality-in-proof and the WASM verifier (horizon rank 19).** Finality-in-proof gives 494 B of public values that say "locked at checkpoint i by x of y weight" under one proof; rank 19 gives the Groth16 or Plonk wrapper verified in a tab. What is left is the last mile: carrying that object with no network, and a "verify this block" link on the explorer.
|
||||
|
||||
**Prior art.** Mina (2021, approximate): a constant-size recursive proof of the whole chain, verified on a phone, with proof-of-stake under it. zkSync's block proofs on Ethereum (2023, approximate): verified by a contract, not a phone. Helios (a16z, 2022): a light client in WASM, trust rooted in a weak-subjectivity checkpoint. No proof-of-work chain has a phone-verifiable finality object; after finality-in-proof Igneum does, and the QR is only packaging.
|
||||
|
||||
**The object (model, section D).** Groth16 proof 260 B (SP1 docs) + public values 494 B + verifying-key id 32 B = 786 B. A version 40 QR at level L holds 2,953 B; a version 25 at level M about 1,000 B (approximate). So one QR carries the proof with room for a block hash and a receipt path of up to about 2 KB, and a phone with the pinned verifying key verifies it with no network: one bn254 multi-pairing, of the order of 2 to 5 ms native on a phone (approximate; Helios and the xycloo WASM demos are the order-of-magnitude anchors, 10 to 50 ms in WASM, frontier 3.4). The URL form is the same bytes base64 in a fragment, 1,048 characters, under every browser's limit.
|
||||
|
||||
**What the phone learns with no network.** That some chain with this chain id, under this aggregator program, had checkpoint i locked by x of y weight, with state root R and history root H, and (with the receipt path) that transaction T is in a block at or below the lock. What it cannot learn offline: that this is the LATEST lock (staleness: the `lock_daa` field lets it show the age against its own clock, labelled "age by your clock").
|
||||
|
||||
**The wrapper's cost on a 5090.** SP1's docs give PLONK as about 1 min 30 s longer than a compressed proof and Groth16 as the recommended on-chain form at 260 B; the wrap runs on the CPU in gnark whatever the GPU (approximate, from memory of SP1's wrapper architecture). So the wrap is not per segment (8 s): one wrapper machine wraps one proof per 90 s, and the chain's "latest wrapped proof" is 38 segments behind the tip at a 300-s cadence or 8 behind at 60 s with two wrapper machines (model, table D). The measurement the design needs is the one frontier 3.4 names (R4): wrap the pinned aggregator proof on a 24 GB fleet card and the box's CPU, record seconds and RAM. Expected, approximate: 60 to 180 s and 16 to 32 GB of RAM.
|
||||
|
||||
**The explorer link.** "Verify this block" on the explorer renders the QR and the URL for the newest wrapped proof whose history covers the block, plus the block's MMR path; the tab verifies it with rank 19's WASM verifier and shows the milliseconds. 4 hours once rank 19 lands.
|
||||
|
||||
**Game theory.** A forged QR fails verification; a stale QR shows its age; a QR from another chain id is refused. A soundness bug in SP1 forges a lock on the phone and not on the chain (full nodes verify the BLS certificate natively, finality-in-proof 5.1), which is the stated light-client row.
|
||||
|
||||
**Hostile review (Kaspa voice).** "A QR that says 'locked' with no network says 'locked as of when the QR was made'. Say the age in big letters or you will have people paying against last week's lock." Correct; the age line is in the design.
|
||||
|
||||
**Cost and gate.** 8 hours (QR and URL packing 2, the phone verify call on the shared `igneum-light` engine 4, the explorer link 2) plus rank 19's 16. Gate: a phone in airplane mode scans a QR printed from the explorer and shows "locked at checkpoint i, x percent of weight, age 4 minutes by your clock, verified in N ms"; a QR with one byte changed is refused.
|
||||
|
||||
**Per tier.** Holders: a proof in their pocket. Miners: their node serves the wrapped proof (section 3). Rollup customers: the same 786 B is what their bridge verifies. Node operators: one wrapper machine per network at a 300-s cadence (a box, not a card).
|
||||
|
||||
**Verdicts.** Cryptographer: do now after rank 19 (the first offline-verifiable PoW finality). Consensus engineer: do now (nothing in consensus). The miner: prototype (nice, not what miners ask for). Economist: do now (the cheapest public proof of the phase-two claim; the wrapper machine is one box).
|
||||
|
||||
---
|
||||
|
||||
## 7. More candidates, generated and judged
|
||||
|
||||
Twelve more. The first four are this lane's own (7.1 to 7.4); the rest are the brief's list, judged.
|
||||
|
||||
### 7.1 A weight-backed peer directory in the coinbase (this lane's candidate)
|
||||
|
||||
**The idea.** A block producer may opt in to carry its node's reachable address (IPv6 plus port, 18 B) in its coinbase extra data, signed by the vote key the header already names. The chain becomes its own seed list: a fresh client draws its outbound peers from the last 30 days of carried addresses, weighted by the carrying keys' W2 weight. No DNS seed, no shipped seed list beyond genesis peers, no operator.
|
||||
|
||||
**Prior art.** Bitcoin's DNS seeds and `addr` gossip (2011 onward, approximate); Ethereum's ENR and discv5 (2019, approximate); Kaspa's dnsseeder (approximate); the eclipse attack paper (Heilman, Kendler, Zohar, Goldberg, USENIX Security 2015) and its address-bucket fixes. No chain this lane knows writes reachable addresses into blocks under the producer's key with weight-weighted draws. New in form; the pieces (addr gossip, weighted sampling) are old.
|
||||
|
||||
**Mechanism.**
|
||||
|
||||
| Item | Rule |
|
||||
|---|---|
|
||||
| Object | `IGNA`-style section: `0x05 ‖ ip16 ‖ port2`, opt-in, at most one per block, signed by the header's vote key (the key reveal already proves possession) |
|
||||
| Directory | every node keeps the last window's entries keyed by vote key hash (one address per key, newest wins) |
|
||||
| Draw | a client picks outbound peers by the key's W2 weight; keys under dust are not drawn |
|
||||
| Bytes | 18 B per block = 1.56 MB a day on every node (model, section E); 30 days at most 2.6 M entries, far fewer after de-duplication by key |
|
||||
|
||||
**Game theory (model, table E).** An attacker with share a of the 30-day weight lists share a of the directory. With 8 outbound peers drawn by weight, the probability every peer is the attacker's is a^8: 2.6e-6 at 20 percent, 1.8e-4 at 34 percent, 4.6e-3 at 51 percent; with 16 peers 6.6e-12, 3.2e-8, 2.1e-5. Against this, a DNS seed is one operator and a shipped list is one release key. The attack that remains: listing honeypot addresses under honest-looking keys costs the attacker 30 days of mining per key, the same bound as every other weight-backed thing on this chain. Griefing: a producer lists a victim's address to draw traffic to it; bounded by one address per key per block and the signed key, so the lister is named on chain.
|
||||
|
||||
**Hostile review (Kaspa voice).** "Your reachable miners are the rigs and the seeds; home miners are behind NAT and will list nothing or list a dead address. The directory is a list of rigs weighted by rigs, which is the thing you said you wanted to avoid (frontier 3.8's Kaspa attack). Also a 30-day-old address is a dead address." Answer: the client draws from the newest entries first with the weight as the tie-break, a liveness probe before use is the ordinary `version` handshake, and the rigs-weighted list is still a list of parties that mined 30 days of public blocks rather than one DNS operator. The NAT point stands and is the measurement.
|
||||
|
||||
**Cost and gate.** 10 hours: the coinbase section and signature check (3), the directory and the weighted draw in the node (4), the client's use in Ember verify mode and the phone (3). Gate: a fresh node with no seed list and genesis peers only reaches 8 outbound peers from the directory on the fast-time network; an eclipse harness with a 34 percent attacker listing 34 percent of addresses eclipses the fresh node in under 0.1 percent of 1,000 starts (expected 1.8e-4 at 8 peers).
|
||||
|
||||
**Per tier.** Home miner: opt-in, off by default (NAT); a rig and a seed node opt in; a pool node opts in; the phone and Ember verify mode gain a seed list with no DNS; no card, OS or vendor difference; node operators store 1.56 MB a day.
|
||||
|
||||
**Verdicts.** Cryptographer: prototype (a weight-backed seed list is the right shape; measure the NAT fraction). Consensus engineer: prototype (10 hours, nothing in fork choice). The miner: watch (home miners will not opt in; rigs will; fine). Economist: prototype (removes one operator from the trust row for 1.56 MB a day).
|
||||
|
||||
### 7.2 Sign-in with Igneum: 30 days of public work as a login (this lane's candidate)
|
||||
|
||||
**The idea.** A vote key signs a challenge to prove its W2 weight to a third party: a rental marketplace, a forum, a faucet, an airdrop-free allowlist. The verifier reads the weight from any node or the finality-in-proof public values. Sybil cost is 30 days of mining per identity.
|
||||
|
||||
**Prior art.** Hashcash (Back, 1997): proof of work as an anti-spam stamp. Sign-In with Ethereum (EIP-4361, 2021): a signed message as a login. Proof-of-humanity and Gitcoin Passport (2021 onward, approximate): social Sybil resistance. No chain has a non-transferable, work-earned weight per key to sign with; Igneum does.
|
||||
|
||||
**Game theory.** A key's weight is worth its 30 days of pool income (work-stake section 5: 2,793 IGN for an 8 GB card at 100 GH/s), so lending it to a login is lending a reputation that cannot be split without halving each half. A marketplace that gates on weight gets a Sybil cost it cannot buy elsewhere. The attack: a key used as a login is a key whose secret is on a machine that talks to websites; the pool spec's one-signer rule (9.6 item 3) and the equivocation strip mean a stolen key is burned by its thief in one double vote. The login must therefore be a delegated session key signed once by the vote key, never the vote key online.
|
||||
|
||||
**Hostile review (Monero voice).** "You are building an identity out of a mining key. Miners do not want identities; that is why they mine." Answer: opt-in, delegated, and the first customer is the rental escrow of frontier 3.13, which already needs the key.
|
||||
|
||||
**Cost and gate.** 6 hours (the delegation message, the verifier library in `igneum-light`, an Ember button). Gate: a web page verifies a delegated signature against a node's weight for one key and refuses a key under dust.
|
||||
|
||||
**Per tier.** Any key above dust (100 blocks a window; 3.86 MH/s at 100 GH/s) can sign in; an 8 GB card qualifies at every network size up to about 440 GH/s (where 17 MH/s falls under dust, arithmetic on work-stake section 5); below that, nothing.
|
||||
|
||||
**Verdicts.** Cryptographer: watch (the delegation design is the whole risk). Consensus engineer: watch (not consensus). The miner: watch. Economist: prototype only with the rental escrow as the first user.
|
||||
|
||||
### 7.3 Public timestamping on the proof chain (this lane's candidate)
|
||||
|
||||
**The idea.** Any hash posted in an Igneum transaction is, 60 to 93 s later, inside a certified checkpoint and, after the segment proof, inside a 786 B offline-verifiable object (section 6). OpenTimestamps (Todd, 2016, approximate) does this on Bitcoin with hours of latency and a Merkle aggregator; Igneum does it with the lock latency and a proof a phone verifies.
|
||||
|
||||
**Game theory.** None: a transaction with a hash. The base fee burns; the priority fee pays. The product is the receipt path in the QR.
|
||||
|
||||
**Cost and gate.** 4 hours (a contract that emits the hash in a log, the receipt path in the explorer link of section 6). Gate: a timestamp verified offline on a phone from a QR.
|
||||
|
||||
**Verdicts.** All four: do now once section 6 lands; it is section 6's first use. The economist: a product with a fee a user pays, which is the kind the utility lane wants.
|
||||
|
||||
### 7.4 Hash-rate futures as an app (this lane's candidate, judged against ruling)
|
||||
|
||||
**The idea.** Braidpool's stated purpose: a miner sells its future shares forward for cash now. On Igneum a miner could sell its next window's pool income (the work-stake bond) forward.
|
||||
|
||||
**Why it dies.** A forward needs a counterparty and collateral; the collateral is a coin balance posted by the seller or buyer, which is a coin bond, which is refused by ruling for anything the protocol touches. As an app between consenting parties with their own escrow it is allowed and the chain is indifferent. Prior art: Braidpool (in development), NiceHash (2014, an operator market). Verdict: never in protocol; watch as a third-party app. 0 hours.
|
||||
|
||||
### 7.5 Finality as a service for other PoW chains
|
||||
|
||||
**The idea.** Another PoW chain posts its block hashes into Igneum and treats the Igneum lock as a checkpoint; its clients refuse reorgs below the last anchored, locked hash. Igneum relies on nothing; others relying on Igneum is allowed by ruling.
|
||||
|
||||
**Prior art.** Komodo dPoW (2018, approximate; the academy page 404'd): notary nodes write a chain's block hash into Bitcoin every 10 minutes (approximate) and protected chains refuse reorgs below it; VeriBlock Proof-of-Proof (2019, approximate; veriblock.org: "securing 1 chain", market cap USD 9 M); Babylon (Tas, Tse, Gai, Kannan, Maddah-Ali, Yu, 2022, IEEE S&P 2023: PoS chains checkpoint to Bitcoin; stake withdrawal from weeks to under 5 hours). Igneum's difference: the anchored chain gets a 90-s lock instead of Bitcoin's hour, and an offline-verifiable proof instead of an SPV path.
|
||||
|
||||
**Game theory.** For Igneum: none; an anchor is a transaction. For the anchored chain: its safety becomes Igneum's finality, which pauses whenever under two thirds of weight signs (spec 03 3.7); an anchored chain must fall back to its own PoW during a pause and say so, which Babylon's design also requires of its consumers. A hostile Igneum majority cannot forge an anchor (it would need two thirds of weight to lock a false chain) but a third of weight can pause anchoring. Market (model, section G): at Komodo's cadence (144 anchors a day) the fee is 0.73 IGN a day, under one cent at any listed price. Revenue to Igneum: nothing material; standing: a product no PoW chain offers with proof-carrying finality.
|
||||
|
||||
**Hostile review (Kaspa voice).** "Who are the customers? Komodo's dPoW protected a handful of Komodo-family chains and VeriBlock secures one chain worth USD 9 M. Small PoW chains that fear 51 percent (ETC, BTG, VTC in 2018 to 2020) chose checkpoints from their own developers over paying anyone." Correct; the demand is the gate.
|
||||
|
||||
**Cost and gate.** 16 hours: an anchor contract (4), a client library that reads the lock and the proof for an anchored hash (8), a reference patch for one open-source PoW node (4). Gate: one outside chain's testnet runs the patch for a week and refuses a staged deep reorg below an anchored lock.
|
||||
|
||||
**Per tier.** Nothing changes for any Igneum miner or holder; an anchored chain's miners get a 90-s checkpoint they did not vote for, which their community must accept (the Komodo precedent: accepted by chains that chose it).
|
||||
|
||||
**Verdicts.** Cryptographer: prototype (sound, and the first external use of the lock). Consensus engineer: watch (demand first). The miner: watch (no miner cares). Economist: watch (USD 0.004 a day per customer at the floor is standing, not income).
|
||||
|
||||
### 7.6 The hourly program as a standing public hardware benchmark with a leaderboard
|
||||
|
||||
Frontier 3.16 (do now, 10 hours) publishes the corpus; the leaderboard is the product on top: per card model, per vendor, per driver, the median rate and joules per hash across the hour's variants, from the fleet library and opt-in Ember submissions, with the bit-exact fingerprint per entry. Prior art: Geekbench (2007, approximate), hashrate.no and WhatToMine (2018 onward, approximate) as operator tables; none continuous on a random kernel stream. New as a continuous random-kernel benchmark. Game theory: a submitter lies about its rate; the fingerprint proves bit-exactness not speed, so self-reported rates are labelled and the fleet's measured rows are the reference tier. Cost 8 hours on top of 3.16. Gate: a public page with the eleven measured cards (prover-tiers-real-cards.md) as the reference tier and opt-in rows beneath. Verdicts: do now (all four; the miner: "a leaderboard is the first thing a mining YouTuber screenshots").
|
||||
|
||||
### 7.7 Proof of unique card: count machines, not keys
|
||||
|
||||
Dead, and the reason is the design's own: nothing in consensus counts nodes, keys or cards (spec 3.1 W6, ledger F17), so a count of machines would be a Sybil surface the design removed on purpose; and a chip emulates any fingerprint it has been shown (frontier 4.3's Monero attack). What the design does count is blocks, and a card's blocks are its weight. Verdict: never. 0 hours.
|
||||
|
||||
### 7.8 Shard proving as the on-ramp: a proving-only key earning pool income without a card
|
||||
|
||||
Dead by the sortition rule: shard assignees are drawn by W2 weight (spec 7.2 step 2); a key with no blocks has no weight and is never drawn. What a proving-only key can do today: claim shards after the exclusive window (spec 7.2 item 4) and prove external jobs under the pool protocol's bonded key route (work-stake section 5, the prover row). That is the on-ramp and it exists. Verdict: never as a rule change; the two existing routes stand. 0 hours.
|
||||
|
||||
### 7.9 Miner-signed release: 95 percent of weight signs the release hash
|
||||
|
||||
Compare horizon rank 13 (reproducible-build attestations, N of M builders, 12 hours). A weight signature says "we RUN this hash", not "we BUILT it", and running is what the 95 percent class signal already measures (the object byte in the header version). So the idea is the P2 signal applied to a binary hash instead of a class byte: a header bit per release. Prior art: BIP9's version bits (2015) for rules, not binaries; no chain signals binary hashes (approximate). What it adds: a chain-readable "share of weight on release X" that Ember can show beside rank 13's "N builders reproduced X". Cost 4 hours (a release-hash field in the coinbase, tallied by the explorer, no consensus effect). Game theory: lying costs nothing and buys nothing; it is a statistic. Verdict: watch, fold into rank 13's display. Cryptographer: watch. Consensus engineer: do as a coinbase field, 4 hours. The miner: watch. Economist: watch.
|
||||
|
||||
### 7.10 An energy-price oracle from miners' own signals
|
||||
|
||||
**The idea.** Each producer carries its electricity price in its coinbase; the window's weighted median is the chain's energy price, free of any oracle operator.
|
||||
|
||||
**Prior art.** Chainlink and the oracle networks (2017 onward, approximate); frontier 3.5's miner-voted dials (Ethereum's gas-limit vote, geth `VerifyGaslimit`); no PoW chain carries a price field (approximate).
|
||||
|
||||
**Game theory.** A field nobody is paid for and nobody is punished for lying in is noise; a field that feeds a price (the job reserve of horizon rank 7, "measured proving electricity per pgas at the published settlement rate") is a field miners overstate, bounded only by the customer's bid, which rank 7 already makes the price. So the oracle is either noise or a lever on the job price that the bid neutralises. The honest use is statistical: the miner community lead's hardware-economics model wants real tariffs, and Ember's opt-in telemetry (the tariff field of phone-app 4.1) gives them off-chain with no consensus bytes.
|
||||
|
||||
**Verdict.** Never in consensus; an opt-in Ember statistic. 2 hours for the telemetry line. All four personas: never in protocol.
|
||||
|
||||
### 7.11 A first-block bonus per new key, Sybil-bounded by dust
|
||||
|
||||
**The idea.** A key's first block above dust pays a bonus B, to welcome newcomers; the signing bonus of vote-or-burn exists, so this would be the second bonus.
|
||||
|
||||
**Game theory (model, section F).** Dust is 100 blocks a window. A 10 percent miner makes 259,200 blocks a window and can keep 2,592 keys above dust, each collecting B once: at B = 100 IGN that is 259,200 IGN a window, 1 percent of its subsidy, for free (weight and sortition are per block, so the split costs it nothing but its own vote's convenience; the pool spec's one-key rule is a client default, not a consensus rule). A bonus large enough to matter to a newcomer (10 blocks, 1,000 IGN) is a 10 percent raise for every miner willing to run 2,592 keys. The dust bound makes the Sybil expensive in KEYS and free in HASH, and keys are free. Verdict: never. 0 hours. The miner: "an instamine with extra steps".
|
||||
|
||||
### 7.12 A cross-vendor parity bounty
|
||||
|
||||
Dead by ruling (no device bounty): a payment conditioned on a card model is a device bounty whatever it is called. What the design does instead is measure (the eleven rented cards, the ladder's per-rung 5 percent rule) and publish. Verdict: never. 0 hours.
|
||||
|
||||
### 7.13 In-protocol insurance against reorgs for exchanges
|
||||
|
||||
**The idea.** An exchange that credited a deposit later reversed by a reorg is made whole from a protocol pool.
|
||||
|
||||
**Prior art.** None shipped in any protocol (this lane found no primary page; approximate). Off-chain: exchange confirmation policies; Nexus Mutual-style cover for contracts (2019, approximate), not for reorgs.
|
||||
|
||||
**Why it dies.** Who pays? A protocol pool is either emission (a transfer from miners to exchanges, which the miner persona rejects on the Decred and Ethereum precedents in vote-or-burn section 2) or the proving pool (a transfer from provers, who did nothing wrong). The design's own answer is stronger than insurance: a certified checkpoint cannot be reversed (51-percent.md section 2), the four-state rule credits on "finalised" (ledger P17), and the exchange guidance says confirm at the lock or at 12 hours during a pause (horizon rank 16). Insurance is only wanted where finality is paused, and during a pause the only reorg-capable party is a hash majority with no slashable bond, by ruling. Verdict: never; the guidance is the product. 0 hours.
|
||||
|
||||
### 7.14 Pause insurance from the proving pool
|
||||
|
||||
Dead: it pays the wrong people or nobody. During a pause the silent third earns its subsidy (51-percent.md: "free to hold while silent"); redirecting pool income to the signing keys is the signing bonus's job (8 of 80 points) and to holders is impossible (no mechanism pays holders). Vote-or-burn priced the pause at 3,103 to 6,206 IGN an hour at 34 percent silent and found a deposit worth attacking covers either; LEAVE and weight-gated fork choice close the line. Verdict: never. 0 hours.
|
||||
|
||||
### 7.15 Proof of latency: rewarding low-latency relays
|
||||
|
||||
The arXiv search for "proof of latency" this morning returned five unrelated papers and none on relay rewards; FIBRE (2016) relays unpaid. Timing is unverifiable in consensus (section 1.4) and the DAG already pays for latency in blue blocks (under 2 percent red at 1 block/s, run B). A relay reward would need a clock and a Sybil-proof receipt, and it has neither. Verdict: never. 0 hours.
|
||||
|
||||
---
|
||||
|
||||
## 8. Verdict table
|
||||
|
||||
Ranked. At most three "do now" and three "prototype"; the rest "watch" or "never" with the line that killed them. Persona order: cryptographer / consensus engineer / the miner / economist.
|
||||
|
||||
| Rank | Candidate | Prior art (name, year) | New? | Survives its game (bound) | Hostile review verdict | Hours | First gate | Four verdicts | Recommendation |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 1 | 6 Phone-verifiable proof in a QR or URL, offline, plus the explorer link | Mina 2021 (approx.), Helios 2022, zkSync block proofs 2023 (approx.) | the offline PoW finality object is new | yes (a forged QR fails; staleness shown by `lock_daa`) | "show the age in big letters": accepted | 8 (+16 rank 19) | phone in airplane mode verifies a printed QR, refuses a one-byte change | do now / do now / prototype / do now | **do now** |
|
||||
| 2 | 5 Heat mode in Ember | Heatbit 2022, 21energy (Austria), Qarnot 2010 (approx.) | new only as a chain client feature | yes (a schedule is a schedule; seasonal hash absorbed by the DAA and the 30-day window) | "every miner GUI has a thermostat": accepted | 6 | set temperature held within 1 degree for 4 h on PC 1 while hash follows the duty cycle | do now x4 | **do now** (UK break-even 5.3 p an hour against gas or a heat pump; free against a resistive panel) |
|
||||
| 3 | 3 Miners' nodes serve the 786 B proof to phones (Ember line, unpaid) | Portal Network 2021 to 2026 (unrewarded), Helios 2022, Mina snarkers 2021 (approx.) | no | yes (a lie fails verification; eclipse bounded by N of M pins) | "not a service you price": accepted | 4 (+20 frontier 3.8) | a phone on cellular shows "locked, voter set verified" from a miner's node | do now x4 | **do now** |
|
||||
| 4 | 7.1 Weight-backed peer directory in the coinbase | DNS seeds 2011, discv5 2019, Heilman 2015 (approx.) | new in form | yes: eclipse at 34 percent of weight 1.8e-4 with 8 peers, 3.2e-8 with 16 (model E) | "a list of rigs weighted by rigs; NAT": partly accepted, the NAT fraction is the measurement | 10 | fresh node with genesis peers only reaches 8 outbound from the directory; 34 percent attacker eclipses under 0.1 percent of 1,000 starts | prototype / prototype / watch / prototype | **prototype** |
|
||||
| 5 | 4 Verifiable payout contract for existing pools (SmartPool shape) | SmartPool 2017; Ocean 2023 (approx.) | no | yes (a dropped share is provable from the member's log) | "P2Pool sidechain if you want no operator": accepted as the alternative | 16 | a member proves a dropped share on the fast-time network and is paid; 32 B per round on chain | prototype x4 | **prototype** |
|
||||
| 6 | 7.5 Finality as a service for other PoW chains | Komodo dPoW 2018 (approx.), VeriBlock PoP 2019 (approx.), Babylon 2022 | the 90-s proof-carrying anchor is new | yes for Igneum (an anchor is a transaction); the anchored chain inherits Igneum's pauses | "who are the customers": demand is the gate | 16 | one outside testnet refuses a staged deep reorg below an anchored lock for a week | prototype / watch / watch / watch | **prototype** (demand-gated) |
|
||||
| 7 | 7.6 Hardware leaderboard on the program corpus | Geekbench 2007, WhatToMine 2018 (approx.); frontier 3.16 | new as a continuous random-kernel benchmark | yes (self-reported rates labelled; the fleet's rows are the reference tier) | none | 8 (+10) | public page with the eleven measured cards as the reference tier | do now x4 | watch (frontier 3.16 is already "do now"; this is its page; the cap of three "do now" is spent) |
|
||||
| 8 | 7.3 Public timestamping on the proof chain | OpenTimestamps 2016 (approx.) | the offline-verifiable receipt is new | yes | none | 4 | a timestamp verified offline from a QR | do now x4 | watch until rank 1 lands, then 4 hours |
|
||||
| 9 | 7.2 Sign-in with Igneum (delegated weight as a login) | Hashcash 1997, EIP-4361 2021 | new (work-earned, non-transferable weight) | yes if delegated (a vote key online is a key a thief burns) | "miners do not want identities": opt-in | 6 | a page verifies a delegated signature against a node's weight; dust refused | watch / watch / watch / prototype | watch (first user: the rental escrow) |
|
||||
| 10 | 7.9 Miner-signed release hash in the coinbase | BIP9 2015; horizon rank 13 | no | yes (a statistic) | fold into rank 13 | 4 | the explorer shows share of weight per release hash | watch / do / watch / watch | watch |
|
||||
| 11 | 1.3 and 1.5 Proof of serving or state availability as a reward slice | Filecoin PoSt 2020, Arweave SPoRA 2021 (approx.), PeerDAS 2024 | the miner-key reward is new | no: outsourced in 108 ms; self-answered under class v5 | "pays for what the lottery compels" | 0 (4 for the unpaid RPC) | | never x4 | never: the unpaid RPC (new-pow rank 6) is all that survives |
|
||||
| 12 | 2 x of 80 points conditional on serving | as 11 | no | no (same bounds; a second liveness bonus on the signing bonus's event) | "merge or drop" | 0 | | never x4 | never |
|
||||
| 13 | 4 In-protocol pool (shares in blocks) | FruitChains 2016, P2Pool 2011, Monero P2Pool 2021, Braidpool (in development) | no | yes on incentives (withholding self-defeating), no on cost: 14.6 to 146 GB a day and 5 to 50 cores per node at 10,000 to 100,000 miners on a 10-s share | "we built a sidechain for this reason" | 0 | | never x4 | never in protocol |
|
||||
| 14 | 1.2 Proof of following | Verthash 2021 (approx.), class v5 2026 | in form only | yes, by doing nothing new | "the root already commits to the chain" | 0 | | never x4 | never: the epoch seed plus class v5 is proof of following |
|
||||
| 15 | 1.4 Proof of propagation receipts | FIBRE 2016 (unpaid) | no | no (time unverifiable; receipts Sybil-free; 94 to 376 MB a day) | "GHOSTDAG is a proof of propagation" | 0 | | never x4 | never |
|
||||
| 16 | 7.10 Energy-price oracle from miners | Chainlink 2017, frontier 3.5 | no | no (noise, or a lever the bid neutralises) | | 2 (Ember telemetry) | | never x4 (in protocol) | never; opt-in statistic |
|
||||
| 17 | 7.11 First-block bonus per key | the signing bonus (vote-or-burn) | no | no: a 10 percent miner collects it 2,592 times a window (model F) | "an instamine with extra steps" | 0 | | never x4 | never |
|
||||
| 18 | 7.13 Reorg insurance for exchanges | none shipped (approx.) | yes | no (no slashable payer by ruling; the lock is the product) | | 0 | | never x4 | never |
|
||||
| 19 | 7.14 Pause insurance from the proving pool | vote-or-burn's pricing | no | no (pays the wrong people) | | 0 | | never x4 | never |
|
||||
| 20 | 7.15 Proof of latency | none found (arXiv, 7 Oct 2026) | yes | no (no consensus clock) | | 0 | | never x4 | never |
|
||||
| 21 | 7.7 Proof of unique card | frontier 4.3 | no | no (nothing counts cards by design; a chip emulates a fingerprint) | | 0 | | never x4 | never |
|
||||
| 22 | 7.8 Proving-only key earning pool income | spec 7.2 | no | dead by the sortition rule; open claims and the pool route exist | | 0 | | never x4 | never |
|
||||
| 23 | 7.12 Cross-vendor parity bounty | | | dead by ruling (device bounty) | | 0 | | never x4 | never |
|
||||
| 24 | 7.4 Hash-rate futures | Braidpool, NiceHash 2014 (approx.) | no | needs a coin bond: dead in protocol | | 0 | | never (protocol) x4 | never in protocol; a third-party app is the chain's indifference |
|
||||
|
||||
Three "do now": ranks 1, 2, 3 (34 hours in all, plus the 36 of rank 19 and frontier 3.8 they sit on). Three "prototype": ranks 4, 5, 6 (42 hours). Everything the brief hoped was a protocol invention in sections 1, 2 and 4 is dead on a number this lane ran: 108 ms (the outsourcing fetch), 0.61 to 76.5 GB a day (challenge answers), 14.6 to 146 GB a day and 5 to 50 cores (in-protocol shares).
|
||||
|
||||
---
|
||||
|
||||
## 9. The honest answer
|
||||
|
||||
Igneum's revolution is the combination already built, not any one new item. The pieces that exist in code or on a branch today and that no shipped proof-of-work chain has together: a random-program GPU hash whose dataset is the chain's own state (class v5, hash rate unchanged at 63.08 MH/s, verifier +0.11 to 0.21 ms), miner-only finality by 30 days of blue blocks with no stake (a lock 63 to 93 s after a checkpoint; 51 percent never reaches two thirds while honest miners mine), that finality carried inside the execution proof as 494 bytes a phone verifies (finality-in-proof), a bond for proving jobs made of 30 days of public work and no coin (work-stake: 2,793 IGN at risk for an 8 GB card at 100 GH/s against a 0.0015 IGN coin bond), a tail that keeps a 24-hour attack at about twelve days of emission in every year, and a pool protocol in which the operator holds no key and no vote.
|
||||
|
||||
This lane tested fourteen candidates for the next thing and found two small ones worth building now (the offline proof in a QR, 8 hours, and heat mode, 6 hours), one Ember line (miners' nodes serve the proof, 4 hours), and three prototypes (a weight-backed peer directory that bounds an eclipse at 1.8e-4 for a 34 percent attacker, a verifiable payout contract for pools, and finality as a service for other chains). Every candidate that would have changed consensus died on a measured number: proof of serving on a 108 ms fetch, in-protocol shares on 14.6 GB a day, propagation receipts on the absence of a clock. The next genuinely new thing is the first offline-verifiable proof-of-work finality object in a user's pocket, and it is packaging on what is already built.
|
||||
|
||||
---
|
||||
|
||||
## 10. Three headline findings for the coordinator
|
||||
|
||||
1. Every "hashing useful to the chain" variant beyond class v5 is dead on one number: a key with no state answers an availability challenge in about 108 ms over a 100 ms link, so any deadline over one block makes the proof "someone served", and the reward slice it would gate (2 to 8 of the 80 points, 17 to 69,120 IGN a day by key size) pays for what the lottery already compels.
|
||||
2. An in-protocol pool is FruitChains (2016) and it costs every node 14.6 GB a day and 5 cores at 10,000 miners on a 10-s share (146 GB and 50 cores at 100,000), so the finished form is the shipped pool v0 plus a verifiable payout contract (SmartPool 2017 shape, 16 hours, 32 B per round on chain).
|
||||
3. The three things worth doing now total 18 hours and change no consensus rule: an offline-verifiable 786 B finality object in a QR (260 B Groth16 + 494 B public values + 32 B key id, under a version-40 QR's 2,953 B), Ember heat mode (a 300 W card breaks even against a UK gas boiler or COP-3 heat pump at 5.3 p an hour earned, and is free against a resistive heater), and every miner's node serving that proof to phones.
|
||||
|
||||
---
|
||||
|
||||
## Sources (accessed 7 October 2026 unless stated)
|
||||
|
||||
Project files: `docs/analysis/horizon-2026-10.md`; `docs/analysis/horizon/frontier.md`; `docs/analysis/horizon/new-pow.md`; `docs/analysis/class-v5-stored-state.md` (branch class-v5); `docs/analysis/finality-in-proof.md` (branch fin-proof); `docs/analysis/work-stake.md` (branch work-stake); `docs/analysis/tail-emission.md`; `docs/analysis/vote-or-burn.md`; `docs/design/latency-ladder.md`; `docs/analysis/51-percent.md`; `docs/plans/pool.md`; `docs/spec/09-pool-protocol.md`; `docs/spec/10-light-client.md`; `docs/design/phone-app.md`; `docs/bench-log.md` (lines 170, 230, 685, 873 to 877, 1934, 2582 to 2635); `.claude/agents/*.md`. Model: `/private/tmp/claude-501/-Users-joshm/cd75457f-4858-4f86-9634-7481ee056b7b/scratchpad/mission-invent/invent_model.py` and `out.md`.
|
||||
|
||||
Primary pages fetched (WebFetch; the session's WebSearch budget was spent before this lane started, so every check is a direct fetch of a known primary page):
|
||||
|
||||
- FruitChains: A Fair Blockchain, Pass and Shi, 2016: https://eprint.iacr.org/2016/916
|
||||
- SmartPool: Practical Decentralized Pooled Mining, Luu, Velner, Teutsch, Saxena, 2017: https://eprint.iacr.org/2017/019
|
||||
- Monero P2Pool (SChernykh): https://github.com/SChernykh/p2pool
|
||||
- Braidpool: https://github.com/braidpool/braidpool
|
||||
- EIP-7594 PeerDAS (2024): https://eips.ethereum.org/EIPS/eip-7594
|
||||
- Portal Network: https://ethportal.net/
|
||||
- FIBRE: https://bitcoinfibre.org/
|
||||
- Helios: https://github.com/a16z/helios
|
||||
- Heatbit: https://www.heatbit.com/
|
||||
- 21energy: https://21energy.com/
|
||||
- Filecoin storage mining and WindowPoSt: https://spec.filecoin.io/systems/filecoin_mining/storage_mining/
|
||||
- SP1 proof types (Groth16 about 260 B, PLONK about 1 min 30 s longer than compressed): https://docs.succinct.xyz/docs/sp1/generating-proofs/proof-types
|
||||
- Ofgem energy price cap unit rates, 1 October to 31 December 2026 (26.32 p electricity, 7.97 p gas, direct debit, national average): https://www.ofgem.gov.uk/your-energy-supply/your-energy-bill/energy-price-cap-unit-rates-and-standing-charges
|
||||
- EIA, average residential electricity price, July 2026 (18.31 cents per kWh): https://www.eia.gov/electricity/monthly/epm_table_grapher.php?t=epmt_5_6_a
|
||||
- Eurostat, electricity price statistics, H2 2025 (EUR 0.2896 per kWh household): https://ec.europa.eu/eurostat/statistics-explained/index.php?title=Electricity_price_statistics
|
||||
- Babylon: Bitcoin-Enhanced Proof-of-Stake Security, Tas, Tse, Gai, Kannan, Maddah-Ali, Yu, 2022: https://arxiv.org/abs/2207.08392
|
||||
- VeriBlock Proof-of-Proof: https://www.veriblock.org/
|
||||
- arXiv search "proof of latency" (no relevant paper): https://arxiv.org/search/?query=%22proof+of+latency%22&searchtype=all
|
||||
|
||||
Pages that returned 404 this morning, so the item is labelled approximate in the text: Arweave yellow paper and mining guide (SPoRA), vertcoin.org Verthash page, Komodo dPoW academy page, Qarnot's Wikipedia page, Ofgem's cap-levels page (the unit-rates page above answered instead). Figures from memory are labelled approximate where they appear: Heatbit's 2022 launch, Qarnot's 2010 founding and 2013 QRad, Komodo's 2018 dPoW and its 10-minute cadence, VeriBlock's 2019 launch, Verthash's January 2021 activation, Mina's 2021 phone verification, EU and US gas prices, phone-side pairing times, SP1's Groth16 wrap time and RAM, Geekbench and WhatToMine years, the Heilman 2015 eclipse paper's venue.
|
||||
186
docs/analysis/mission/mission.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# The Igneum Mission
|
||||
|
||||
Written 7 October 2026, morning UK, by the mission coordinator (branch `mission`, worktree `igneum-wt-mission`) on the project lead's order of the same morning, verbatim: "i dont want to get stuck in a loop here where we keep finding and adding features and security so im going to give you one last mission, deep deep dive into the past and look far into the future, what has been done, what hasnt been done, what has been started but not finished, what can we invent and what can we reinvent to honestly make the perfect gpu network that will be noticed as the revolution that everyone is waiting for."
|
||||
|
||||
This is the last research round. Its output is the closed list in section 2. After it the project stops researching and ships. Five lanes ran in parallel and each has its own file with sources and a verdict table: `past.md` (lane 1), `unfinished.md` (lane 2), `future.md` (lane 3), `invent.md` (lane 4), `reinvent.md` (lane 5). Every number below names its lane; the lane file names the source or the model. Hours are agent hours. Nothing on the live devnet was touched and no build or hash measurement was run for this document.
|
||||
|
||||
## 1. One page for the project lead
|
||||
|
||||
**What has been done.** Of 31 GPU-mined chains since 2011, 8 lost the GPU lane to a chip, 7 closed it by choice, 10 died on price or a rental attack; the 6 still GPU-only pay USD 0.22 to 0.71 a day per RTX 3080 (lane 1). In 20 miner posts the wants were hardware that keeps its value, income without a cliff, a fair supply. Igneum answers the supply in full (no premine, no fund, no fee to any team), the hardware with a measured 2.1x chip bound and the N ladder, the cliff with a monthly glide and a 1 percent tail. Finality by 30 days of blocks, proving as the reward, the leave item and the signing bonus are built or approved for the testnet genesis.
|
||||
|
||||
**What has not been done.** Nobody on those forums asked for finality, a DAG or proofs; they asked for a card that keeps its value, a bill that gets paid and a coin that sells. The front page leads with the hash, the earnings page shows £0.00, a fresh install opens with two operating-system warnings and ends its first hour without one number the miner came for, and no proving customer has signed, so the coin has Ergo's 2023 exposure: a thin market. These gaps are hours, not protocol.
|
||||
|
||||
**Started and not finished.** Three designs sit on branches: class v5 (the dataset is the chain's own state), finality inside the segment proof, work-stake (a bond of 30 days of work, no coin). Across the industry (lane 2), every useful-work scheme that passed cheap-verify did so by making the work useless, no pool without an operator exists on any chain but Monero, and every proof-of-work finality that held was a second validator set bought with coins. Lane 4 tested whether anything new in consensus beats these; each died on a number: proof of serving on a 108 ms fetch, in-protocol pool shares on 14.6 GB a day per node at 10,000 miners, propagation receipts on the absence of a clock, proof of following because the epoch seed plus class v5 already is one.
|
||||
|
||||
**What we invent.** One thing, and it is packaging on what is built: the first offline-verifiable proof-of-work finality object: 786 bytes in a QR a phone verifies in airplane mode, served by every miner's node. No PoW chain has put finality in a user's pocket. Beside it, two genesis bytes the future demands (lane 3): a signature-scheme byte with a key-succession slot, because a post-quantum vote at 8,192 voters costs 57 GB a day unaggregated and the flip must be signalled before a machine exists; and a cache-size rung on the ladder, because a 256 MB consumer cache is a 2 to 3x shortcut.
|
||||
|
||||
**What we reinvent.** The miner's first month. The 30-day vote window is a level system no chain has, and it is free: first block, 100 blocks for a vote, a signature in a checkpoint, 30 of 30 days, rank by weight, a signing streak. Every rung is a chain fact from one RPC; a renter cannot top it inside 30 days; no fund pays for it. Lane 5 priced the first month at about 35 hours: the Poisson count-up before the first block (an 8 GB card at 100 GH/s waits 1.6 hours expected), the block card, earnings in IGN first, the weight leaderboard, the hardware census. In a 105-post Reddit sample the miner's own posts rise highest ("my card paid for itself" at 777 times the room median against 66 for the network's milestone), so every shareable surface carries the miner's card, rank and days, never the project's number.
|
||||
|
||||
**The honest answer.** Igneum's revolution is the combination already built plus two new things: the finality object in the pocket, and the ladder shown everywhere. No shipped chain has the combination: a random-program GPU hash whose dataset is the chain, miner-only finality with no stake that 51 percent never reaches, that finality inside the execution proof, a work bond with no coin, a tail that holds a 24-hour attack at twelve days of emission in every year. Twelve items, about 290 agent hours, no new consensus rule beyond the three cut branches.
|
||||
|
||||
### Decisions owed from the project lead
|
||||
|
||||
The cryptanalysis entity and prize; the signing certificates (lane 5); the signed proving customer as a mainnet gate (lane 1); the H100 and A100 hash measurement on rented pods (lane 3).
|
||||
|
||||
## 2. The closed list
|
||||
|
||||
Twelve items, in build order. "In flight" names the lane or branch that holds it. Hours are the remaining agent hours; a gate is what must be true before the item is called done. Nothing is added to this list without removing something.
|
||||
|
||||
| # | Item | State | Hours | Gate |
|
||||
|---|---|---|---|---|
|
||||
| 1 | The testnet genesis re-cut as approved | in flight (ship lane) | 0 new | the go checklist |
|
||||
| 2 | Finality in the pocket: finality in the proof, the 786 B object, every node serves it, the browser verifier | in flight (fin-proof) plus new | 68 | a phone in airplane mode verifies a printed QR and refuses a one-byte change |
|
||||
| 3 | Class v5, the dataset is the chain, and nothing wider | in flight (class-v5) | 16 | the fast-time harness across a day boundary; Devnet 2 across a real day |
|
||||
| 4 | Weight-gated deep fork choice before mainnet | in flight (horizon rank 4) | 16 | a 51 percent fresh-key fork from 15 min back refused by every honest node |
|
||||
| 5 | Work-stake: the job bond made of 30 days of work | in flight (work-stake) | 24 | 1,000 posted jobs, every injected miss forfeits, zero honest forfeits |
|
||||
| 6 | The ladder shown everywhere: the miner's first month | new (lane 5) | 35 | a Devnet 2 key walks every rung on screen; first share within 10 minutes in 9 of 10 fresh Windows installs |
|
||||
| 7 | Heat mode, the region and price prompt, the cost-against-rent line | new (lanes 3, 4) | 10 | a set temperature held within 1 degree for 4 h on PC 1 while hash follows the duty cycle |
|
||||
| 8 | Genesis forward-compatibility: the scheme byte, key succession, the cache rung | new (lane 3) | 10 | the digest test; a vote item with scheme 1 refused by every node until the signal |
|
||||
| 9 | A second proof system on the active list and the one-third stop switch | new (frontier I4, lane 3) | 24 | 1,000 segments agree across both systems; one injected bad proof disagrees and pays nothing |
|
||||
| 10 | The launch pack: gates and text, no protocol | new (lanes 1, 3, 5) | 15 | every gate line in testnet-go.md with its check |
|
||||
| 11 | The pool finished: pool-0 with member keys at 1 percent, TLS, the share sidechain with no operator | new (lanes 2, 4, 5) | 60 | 100 members on the fast-time network paid by the coinbase rule from a share chain with no operator key; the first on a DAG chain |
|
||||
| 12 | The weight-backed peer directory | new (lane 4, prototype) | 10 | a 34 percent attacker eclipses under 0.1 percent of 1,000 fresh starts |
|
||||
|
||||
### 2.1 The testnet genesis re-cut as approved (in flight, ship lane)
|
||||
|
||||
What it is: one cut with 18 decimals, `EmissionSchedule::TESTNET_1` (100 IGN a block, a monthly glide with a two-year half-life, a 90-day ramp from 10 percent, a 1 percent tail from year 11.4), and the switches on from genesis: proof verification in consensus, the leave item, the signing bonus at 1,000 bps, finality v3, the latency ladder at rung 0. the project lead approved it on 7 October 2026, 09:3x UK (ledger-decisions.md).
|
||||
Why it is right: lane 1's verdict rows 4 and 5 (the cliff and the premine) are answered by it in full; lane 3's tail row holds to year 200.
|
||||
Evidence: tail-emission.md one page; vote-or-burn.md section 5; ledger-decisions.md 7 October.
|
||||
Cost: 0 new hours; the ship lane's own plan.
|
||||
Gate: the go checklist (`docs/plans/testnet-go.md`); nothing mines until the word.
|
||||
Order: first; every later item lands on this genesis.
|
||||
|
||||
### 2.2 Finality in the pocket (in flight on `fin-proof`, plus three new parts)
|
||||
|
||||
What it is: the weight table carried inside the recursive segment proof (W2 updated one mergeset per segment, the BLS certificate verified in the guest, 494 B of public values), then three parts this mission adds: a Groth16-wrapped proof as a 786 B object (260 B proof, 494 B public values, 32 B key id) in a QR or URL that a phone verifies offline; every miner's node serving the latest object by default (an Ember line, unpaid); and the WebAssembly verifier in the tab (horizon rank 19).
|
||||
Why it is new: no proof-of-work chain has an offline-verifiable finality object; the closest shipped forms are Mina's recursive state proof (2021) and Helios's committee light client (2022), both online and neither over proof of work. Lane 4 section 6 and 3, persona verdicts do now, do now, prototype, do now.
|
||||
Evidence: finality-in-proof.md sections 1 to 5 (level 1 of the blue-set check designed, level 0 in the prototype); the certificate half verifies in 58 to 68 ms warm in a browser (bench-log round 6); SP1 light verifier 0.032 s on a Mac core; a version-40 QR holds 2,953 B.
|
||||
Cost: 40 for the fin-proof remainder (level 1 of 5.2, the cycle counts of section 6.1 on the box, the 5090 prover time on a pod), 8 for the QR object, 4 for the Ember line, 16 for the WASM verifier. 68.
|
||||
Gate: a phone in airplane mode verifies a printed QR and refuses a one-byte change; "locked, voter set verified" on cellular from a miner's node; a block proof verifies in the tab under 500 ms on a laptop.
|
||||
Order: second; it is the one invention and it needs the genesis of item 1 for the pinned aggregator id.
|
||||
|
||||
### 2.3 Class v5, the dataset is the chain, and nothing wider (in flight on `class-v5`)
|
||||
|
||||
What it is: each day's dataset built from the execution state at a reference block an hour before the day; a card without the state builds every item wrong. The branch widened it to a per-window refresh (section 2a of its page). This mission's ruling: no further widening. Lane 4 tested proof of following, serving, propagation and state availability as additions and found each dead on a number: a key with no state answers an availability challenge in about 108 ms over a 100 ms link, so any deadline over one block proves only that someone served; propagation receipts need a clock the DAG does not have; the epoch seed plus the state root already commit to the chain's recent blocks, so proof of following is class v5 by another name.
|
||||
Why it is right: hash rate and watts unchanged within noise (63.083 against 63.088 MH/s, 205 to 208 W on a 4090), build +1.4 ms, verifier +0.11 to 0.21 ms per unit; it removes the f = 0 recompute chip as a category and the stateless pool miner.
|
||||
Evidence: class-v5-stored-state.md; new-pow.md 5.2 and 6; invent.md sections 1 and 8 rows 11 to 15.
|
||||
Cost: 16 (the 4090 rows owed in section 7, the exec snapshot wire version 2, the GPU hosts' leaf buffer, the spec text).
|
||||
Gate: the fast-time harness crosses a day boundary with the roots agreeing on every node and the stateless node's miner at 0 accepted blocks; Devnet 2 across a real day boundary; then the 95 percent signal with a floor.
|
||||
Order: third; it rides the class-signal machinery item 1 ships.
|
||||
|
||||
### 2.4 Weight-gated deep fork choice before mainnet (in flight, horizon rank 4)
|
||||
|
||||
What it is: a tip whose fork point is older than D (10 min of past-median time) is a fork-choice candidate only if the keys that built it hold at least a third of the weight table at the fork point.
|
||||
Why it is right: lane 1's verdict row 6: rented hash reorganised Bitcoin Gold, Vertcoin and Musicoin for USD 70 k to 18 M; Igneum's finality bounds this after day 20 and not before, and the first 20 days are when a new chain is watched. The 51 percent paper prices a 12-hour double spend during a pause at USD 146 at 1 GH/s today.
|
||||
Evidence: 51-percent.md section 5 rank 2; horizon rank 4; past.md section 4 row 6.
|
||||
Cost: 10 to 16.
|
||||
Gate: fast-time: a 51 percent fresh-key fork from 15 min back is refused by every honest node; a one-third-weight fork is accepted; partition heal unchanged.
|
||||
Order: fourth; before the public testnet opens, because the testnet is the first chain outsiders can rent against.
|
||||
|
||||
### 2.5 Work-stake (in flight on `work-stake`)
|
||||
|
||||
What it is: a vote key may claim an external proving job only if its next window of pool income covers the job's value; a miss, refusal or forgery forfeits that income for one window, the buyer's fee refunded first, the rest to the pool. No balance moves; no coin is posted. The spec sentence lands first: "no coin stake; the only thing at stake is 30 days of public work."
|
||||
Why it is right: a bond nobody can buy (2,793 IGN at risk for an 8 GB card at 100 GH/s against a 0.0015 IGN coin bond); every attack in its section 6 fails on the window arithmetic.
|
||||
Evidence: work-stake.md sections 3 to 7; frontier 3.2 and 4.1.
|
||||
Cost: 24 (orders carried in blocks, the term unwound on reorg, the finality report wired to `judge_at`, the snapshot field, the deadline floor).
|
||||
Gate: Devnet 2: 1,000 posted jobs with 10 percent injected misses, every injected fault forfeits, zero honest keys forfeit; a 60 s partition across 100 deadlines forfeits nothing.
|
||||
Order: fifth; it needs the job market of spec 5.4, which the first customer (item 10) needs.
|
||||
|
||||
### 2.6 The ladder shown everywhere: the miner's first month (new, lane 5)
|
||||
|
||||
What it is: the 30-day vote window as the status ladder on every surface, and the first month's moments built around it. The rungs: first block; 100 blocks and "your key has a vote"; "your signature is in checkpoint K"; 30 of 30 days, full weight; rank by weight; the signing streak; "your card proved shard N of block M". The parts: the Poisson count-up before the first block ("a card like yours finds a block about every 1.6 hours on today's network; chance so far 31 percent"); the block card with a locally drawn PNG and the explorer link; Earnings in IGN first (6,912 IGN a day for a 100 MH/s card at 100 GH/s at the full rate) with a typed price never fetched; the weight leaderboard on `/live` (weight, never hashrate, so a renter sits at the bottom for 30 days); the public address profile behind an opt-in; the hardware census on `/miners` from the hourly program's per-card timings (frontier 4.3); #first-blocks and the Voter and Window roles granted from `getFinalityWeights`.
|
||||
Why it is the right reinvention: lane 5's archive sample shows the joy posts are the first block, the first payout and the rig photo In a 105-post sample the miner's own posts rise highest: "my card paid for itself" at 777 times the room median against 66 for the network's milestone.; Helium, Chia, Ethereum 2017 and Kaspa 2022 each had a hook, a daily ritual and a status surface, and each status surface was a company number or a hashrate a renter could top. Igneum's is a consensus fact. Nobody has it.
|
||||
Evidence: reinvent.md sections 1, 2, 3.2 to 3.7, 7; the solo-block expectations per tier at 100 GH/s and 1 TH/s (an 8 GB card: 1.6 h and 16 h; it never reaches the vote line at 1 TH/s, which is why the pool is item 11; the 100-block dust line means an 8 GB card never votes past about 500 GH/s, a question for the finality lane and a watch item, not a list item).
|
||||
Cost: 35 (Poisson line 2, block card 4.5, earnings 6.5, ladder 6, leaderboard 3, profile 3, census 4, Discord 5, the first-hour timeline on `/miner` 2).
|
||||
Gate: a Devnet 2 key walks every rung on screen in a view test; every number on the tab names its RPC field on hover; "first share within 10 minutes of download in 9 of 10 fresh Windows installs" published on `/evidence`.
|
||||
Order: sixth; it is the first thing the first outside miner sees.
|
||||
|
||||
### 2.7 Heat mode, the region and price prompt, the cost-against-rent line (new, lanes 3 and 4)
|
||||
|
||||
What it is: Ember holds a room temperature or a schedule and the hash follows the duty cycle; a per-region electricity-price prompt with a banned-region line ("mining may be restricted where you are; you are responsible for checking"); the Earnings tab's cost line against the measured rental rate so a miner sees when rented hash undercuts their card.
|
||||
Why it is right: a 300 W card as a heater breaks even against a UK gas boiler or a COP-3 heat pump at 5.3 p an hour earned and is free against a resistive panel (lane 4 section 5); the heating season credits about a third of a UK or EU miner's bill (lane 3 section 4); lane 3's 2031 row says retail-power tiers leave first when idle datacentre hash sets the rent, and the heat mode keeps winter home miners on. Prior art is Heatbit (2022), 21energy and Qarnot; new only as a chain client's feature, which is the point.
|
||||
Evidence: invent.md section 5 and rank 2; future.md sections 3, 4 and 10.
|
||||
Cost: 6 heat mode, 2 region prompt, 2 the rent line. 10.
|
||||
Gate: a set temperature held within 1 degree for 4 h on PC 1 while hash follows the duty cycle; the prompt shows the right banned list for a Russian region; the rent line reads the bench-log rate.
|
||||
Order: seventh; app only, no digest, ships in any cut.
|
||||
|
||||
### 2.8 Genesis forward-compatibility: the scheme byte, key succession, the cache rung (new, lane 3)
|
||||
|
||||
What it is: three genesis fields. A `sig_scheme` byte in the vote item and the key reveal (0 = BLS12-381 today), so a post-quantum scheme flips by the 95 percent signal and not by a fork. The key-succession item (W5, unimplemented on the node) so a key can hand its weight and its forfeit term to a successor once, which is the migration path. A cache-size rung on the signal ladder beside N, because a consumer last-level cache at the cache size (256 MiB) gives that card's owners a 2 to 3x shortcut (chip-model-v3; consumer LLC is 96 to 128 MB today, datacentre 256 MB). The class-group VDF's quantum fallback is flagged, not sized.
|
||||
Why it is right: naive ML-DSA-44 votes at 8,192 voters cost 19.4 MB a checkpoint and 57 GB a day; with 250x SNARK aggregation about 223 MB a day; a cryptographically relevant quantum computer is "quite possible (28 to 49 percent)" within ten years (Global Risk Institute 2025) and NIST deprecates ECDSA and EdDSA after 2030. The flip is signalled the year of deprecation, not the year a machine appears; the byte costs nothing now and a fork later.
|
||||
Evidence: future.md section 7 and 10; finality-in-proof.md 7 (W5 absent).
|
||||
Cost: 10 (the byte and the digest test 4, the W5 item 6, the rung 1 inside the ladder code, the spec text).
|
||||
Gate: the digest test; a vote item with scheme 1 refused by every node until the signal; a succession carried once and refused twice in the fast-time harness.
|
||||
Order: eighth; genesis fields must land before item 1's final cut, so this is scheduled with it and listed here for its own gate.
|
||||
|
||||
### 2.9 A second proof system on the active list and the one-third stop switch (new, frontier I4 and lane 3)
|
||||
|
||||
What it is: a second zkVM behind the `ProofSystem` trait (RISC Zero or OpenVM) proving the same segments on one fleet box as a shadow; a disagreement between the two on any segment is a public alert and the pool stops paying on proofs until 1/3 of weight signals which system to trust. Full nodes keep the native-execution veto whatever happens.
|
||||
Why it is right: three soundness-class events across SP1 and RISC Zero in 18 months (lane 3 section 5); the veto protects full nodes and nothing protects a light client or the proving pool; lane 3's death row 4 is this.
|
||||
Evidence: future.md sections 5 and 9; frontier I4; ledger P7 and D6.
|
||||
Cost: 24.
|
||||
Gate: 1,000 segments agree across both systems; one injected bad proof disagrees, pays nothing and raises the alert.
|
||||
Order: ninth; before any phone trusts item 2's object.
|
||||
|
||||
### 2.10 The launch pack: gates and text, no protocol (new, lanes 1, 3 and 5)
|
||||
|
||||
What it is: lines added to `docs/plans/testnet-go.md` and the litepaper, each with its check. Gates: a signed proving customer before mainnet, with the weight of the Devnet 2 gate; per-tier income published at three prices (USD 0.005, 0.02, 0.10) before the testnet, per the consequences rule; the launch-week hash-origin report (key counts, pool shares, fleet shares) from the observer, daily for the first 90 days; the first 100 keys, the first 1,000 independent keys (the X5 definition), the first pool not run by the project, the first outside-reproduced benchmark, the first block from a card the project does not own; signed and notarised installers (Developer ID, Authenticode) with the measured first-share time per release. Text: the three wants on the front page and finality on page two; "a block pays its miner whether or not anyone buys a proof that day"; the eight regulatory sentences of lane 3 section 8.3 (no issuer, no sale; the sustainability indicators per era; no price language; the pool holds no balance; no privileged key), labelled "not legal advice; counsel is engaged".
|
||||
Why it is right: lane 1's shape (income halves 60 to 120 days after a peak, under USD 0.10 per kWh within 6 to 18 months, 50 to 100 percent of hash gone in 90 days) and its rows 2, 4, 7 and 9; lane 5's install findings (SmartScreen warns on any file without reputation); lane 3's regulation rows (MiCA 4(3)(b) exempts block-reward assets; the UK regime from 25 October 2027 covers platforms and custody, not mining).
|
||||
Evidence: past.md sections 3 and 4; future.md section 8; reinvent.md 3.1 and 4.1.
|
||||
Cost: 15, plus the project lead's certificates and the customer's signature.
|
||||
Gate: every gate line in testnet-go.md with its check; the forbidden-strings check passes on the new text; the hash-origin report posts on Devnet 2 for seven days before the testnet go.
|
||||
Order: tenth; text and gates can land any hour, and the customer gate is the one that takes longest.
|
||||
|
||||
### 2.11 The pool finished (new, lanes 2, 4 and 5)
|
||||
|
||||
What it is: pool-0 run by the project at the testnet go with the member's vote key in every header (the pool holds no weight and no vote), a 1 percent fee published beside the dev fee, PPLNS with a round every 60 s; TLS before the public testnet (spec 9.3) and the pool page rows Q69 to Q73; then the pool without an operator: a P2Pool-style share sidechain in the pool crate, shares forming a 10-second chain committed in the coinbase extra data, the window's payout computed by every node from consensus data as the 20 percent already is, no balance and no operator key, every member running the node the design already assumes. Lane 2's verdict: the finished form is Monero P2Pool's, not SmartPool's (SmartPool's on-chain claims died on gas, and at 1.35 ms per share verification a sampled contract would still need the zkEVM to re-derive a warp unit per sampled share); lane 4's in-protocol pool (shares inside blocks, every node verifying every share) stays refused at 14.6 GB a day per node.
|
||||
Why it is right: no pool without an operator exists on any chain but Monero (P2Pool at 7.7 percent of hash, 3,520 miners, 0 percent fee; Ocean 2.05 percent of blocks with 85 percent through DATUM; Stratum V2 job declaration at 1 block in 52,297); Igneum already holds the three objects that make one cheap (the vote key in the header, the 20 percent paid by sortition, the coinbase extra data), and nobody has built one on a DAG chain. An 8 GB card at 1 TH/s finds a solo block every 16 hours and 23 percent of its days have none, so the first-month ladder of item 6 depends on a pool from about 1 TH/s.
|
||||
Evidence: unfinished.md section 5 (5.2, 5.3) and headline 2; invent.md section 4 and rank 13; reinvent.md 3.5 and 1.4; pool.md; spec 09.
|
||||
Cost: 60 (TLS 8, the pool page rows about 12, the share sidechain about 40).
|
||||
Gate: 100 members on the fast-time network paid by the coinbase rule from a share chain with no operator key, a withheld share earning nothing, a member's dropped share provable from its own log; the first payout inside two minutes of the first share for an 8 GB card on Devnet 2.
|
||||
Order: eleventh; pool-0 and TLS before the testnet go, the share sidechain before the network passes about 1 TH/s.
|
||||
|
||||
### 2.12 The weight-backed peer directory (new, lane 4, prototype)
|
||||
|
||||
What it is: a coinbase section where a key above dust may publish its node's address; a fresh node dials from that list weighted by 30-day weight, beside the DNS seeds.
|
||||
Why it is right: lane 4's model E bounds an eclipse by a 34 percent attacker at 1.8e-4 with 8 outbound peers and 3.2e-8 with 16; prior art (DNS seeds 2011, discv5 2019, Heilman's eclipse paper 2015) weights nothing by work; lane 3's 2031 death row (weight concentrating in hosting firms) is first seen through such a list.
|
||||
Evidence: invent.md section 7.1 and rank 4; horizon rank 9 (the peer floor it extends).
|
||||
Cost: 10.
|
||||
Gate: a fresh node with genesis peers only reaches 8 outbound from the directory; a 34 percent attacker eclipses under 0.1 percent of 1,000 fresh starts; the NAT fraction measured on the fleet.
|
||||
Order: last; a prototype, dropped if the NAT fraction makes the list useless.
|
||||
|
||||
## 3. Rejected, with the one line that killed each
|
||||
|
||||
| Idea | The line |
|
||||
|---|---|
|
||||
| A pool inside the protocol (shares in blocks, no operator) | FruitChains (2016): 14.6 GB a day and 5 cores per node at 10,000 miners on a 10 s share; 146 GB and 50 cores at 100,000 |
|
||||
| A reward slice for serving state or data | a key with no state answers in 108 ms over a 100 ms link; it pays for what the lottery already compels |
|
||||
| Proof of propagation receipts | no consensus clock; 94 to 376 MB a day of receipts; GHOSTDAG already is a proof of propagation |
|
||||
| Proof of following beyond class v5 | the epoch seed and the state root already commit to the recent chain |
|
||||
| A first-block bonus per key | a 10 percent miner collects it 2,592 times a window: an instamine with extra steps |
|
||||
| Reorg insurance for exchanges, pause insurance from the pool | no slashable payer by ruling; the lock is the product; the pool pays the wrong people |
|
||||
| Proof of latency, proof of unique card, an energy-price oracle, hash-rate futures in protocol | no clock; nothing counts cards by design; a lever the bid neutralises; needs a coin bond |
|
||||
| Finality as a service for other chains | kept as a demand-gated prototype only; no customer named, so not on the list |
|
||||
| The weight-aged reward rule (pay per block falls when hash arrives faster than the weight) | a 30-day income ramp on every honest newcomer, which the first-month ladder cannot afford; revisit only if weight by hosting provider passes a third |
|
||||
| Mining is proving; useful work in the hash | 2.9 MB of openings per block, a 32 to 40 ms verify against the 10 ms gate; Aleo's fastest-prover-wins |
|
||||
| Proving others' chains as the main income | all of Ethereum L1's proving is about USD 36 a day against USD 13,700 of year-1 emission |
|
||||
| Vote-or-burn | 70 to 80 percent respect against the 95 percent test; the signing bonus is on at genesis instead |
|
||||
| Dormant-coin rent, any holder penalty, a device bounty, a coin stake, Bitcoin anchoring, privacy | refused by ruling |
|
||||
| A hardware wallet verifying proofs on the secure element | a bn254 pairing on a Cortex-M class element is seconds to minutes; the companion verifies |
|
||||
| A compute market for unverifiable work | an escrow without a verifier is a trust-me payment with lower fees |
|
||||
| Merged mining, multi-algo, an auxiliary chain | A child inherits its parent's pool concentration and loses its say in rules; a second hash lane splits the weight table and the DAA (Myriad, Verge, DigiByte); the one merged-mining shape with value is the share sidechain of item 11 |
|
||||
| Gamification paid from a fund, referral in coins, an airdrop for anything, NFT badges, off-chain points, a rank-by-hashrate board, a top-earners board in fiat, streaks that demote | there is no fund; nothing is for sale; the rung is a chain fact; a renter tops hashrate on day one; the burn was refused |
|
||||
| Any number that needs a company server to be trusted | every number shown is reproducible from a node, or it is not shown |
|
||||
|
||||
## 4. What this round did not run
|
||||
|
||||
| Item | Why | Where it sits now |
|
||||
|---|---|---|
|
||||
| The H100 and A100 hash rate on rented pods | no pod rented for this round | item 10's measurement line; lane 3 section 3 |
|
||||
| The in-guest BLS and colouring cycle counts (fin-proof 6.1) | no box slot taken for this document | item 2's cost |
|
||||
| The 4090 rows for class v5 (section 7 of its page) | the pod window was the class-v5 lane's | item 3's cost |
|
||||
| Reddit, bitcointalk and the Wayback Machine | refused this environment's fetches; quotes came through search snippets and project forums | past.md section 2, reinvent.md section 1, each labelled |
|
||||
|
||||
## 5. The closing line
|
||||
|
||||
Nothing is added to this list without removing something. Signed, the mission programme, 7 October 2026.
|
||||
304
docs/analysis/mission/past.md
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
# The past, honestly: every GPU-mined chain since 2011 and what GPU miners want
|
||||
|
||||
Lane 1 of the last mission. Written 7 October 2026 by the past lane for the mission coordinator. Scope: the history of every chain that mattered to GPU miners, what each promised, what each paid, when the GPU miners left, and what the record says Igneum should change. The ASIC side of this history (which hash fell to which chip and how fast) is already in `docs/analysis/asic-resistance-history.md` and is not repeated; this file cites it where a chip is the reason a chain died for GPUs. The emission and governance comparison against Kaspa, Monero, Ethereum and the rollups is in the Horizon economy lane (section 4.6) and is not repeated either.
|
||||
|
||||
Every figure from a source carries a URL and the access date in section 6. Every figure from memory is labelled approximate. Reddit, bitcointalk and the Wayback Machine refuse this environment's fetches (sections 2 and 6 say which quotes came through search snippets instead). Times are UK time.
|
||||
|
||||
## 0. Progress
|
||||
|
||||
| Time (UK, 7 Oct 2026) | State |
|
||||
|---|---|
|
||||
| 08:05 | Read CLAUDE.md, asic-resistance-history.md, horizon-2026-10.md section 1, economy-and-utility.md 4.6 |
|
||||
| 08:10 to 08:45 | 75 web searches and 60 fetches across project blogs, pool blogs, forums, press (search budget exhausted at 08:40) |
|
||||
| 08:49 | Writing; sections 1 to 6 landed in one pass |
|
||||
| 08:53 | Length check (258 lines, under the 300 floor); added 1a (the Merge week) and 1b (promised against delivered), split the grouped chain paragraphs, reconciled headline 1's counts against the table |
|
||||
| 08:58 | Done. File complete, reported to the coordinator |
|
||||
|
||||
## 1. One row per chain
|
||||
|
||||
Income columns are USD per card per day before electricity unless stated. "Exit" is when the median GPU miner stopped earning more than electricity at USD 0.10 per kWh, or when the lane closed outright. State is October 2026.
|
||||
|
||||
| Chain (hash) | Launch | What it promised GPU miners | Peak GPU income (card, date) | Exit income (card, date) | GPU miners left | What killed the GPU lane, or state today |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Litecoin (scrypt) | 13 Oct 2011 (approximate) | Memory-hard hash mineable on CPUs and consumer GPUs; no ASIC existed (litecoin.watch) | 200 to 800 kH/s per Radeon, 2012 to 2013; USD per card approximate 1 to 5 | 2014, under power for any GPU once Zeus 28 MH/s at 800 W shipped | Mid 2014 | Scrypt ASIC (Gridseed Jan 2014, Zeus mid 2014, Antminer L3 2017). Today an ASIC chain merged with Dogecoin |
|
||||
| Dogecoin (scrypt, AuxPoW) | 6 Dec 2013 | A fun scrypt coin for the same GPUs as Litecoin | Approximate USD 1 to 3 per card, Jan to Feb 2014 | Aug 2014 | Aug to Sep 2014 | Merged mining under Litecoin from block 371,337 (28 Aug 2014); hashrate +1,500 percent in a month (Binance Research). GPUs never came back |
|
||||
| Ethereum (Ethash) | 30 Jul 2015 | GPU-friendly memory-hard hash with a promise to leave for proof of stake | USD 0.233 per MH per day, Jan 2018 (so USD 23 per 100 MH); USD 6.35 to 9.15 per RTX 3080, early 2021 (Minerstat via Wccftech); May 2021 approximate USD 12 to 15 per 3080 | USD 1 to 2 per 3080 on 14 Sep 2022 (approximate) | 15 Sep 2022, all at once | The Merge. About 900 TH/s and about 20 million GPUs (Tom's Hardware estimate) lost 94 percent of GPU income in one block (2Miners: USD 24 M a day to USD 1.2 M) |
|
||||
| Ethereum Classic (Etchash) | Jul 2016 fork | A home for Ethash GPUs after the Merge | 70.5 to 158 TH/s in 7 hours on 15 Sep 2022 (CoinDesk), 296 to 312 TH/s inside a day | Under electricity for most GPUs within weeks; hashrate -48 percent in four days (AMBCrypto) | Sep to Dec 2022, then again as E9 Pro and Jasminer ASICs took the chain | Today 130 to 186 TH/s, ASIC-dominated; best ASIC nets USD 3.64 a day at USD 0.10 (asicminervalue) |
|
||||
| Monero (CryptoNight to RandomX) | 18 Apr 2014 | CPU and GPU mining, fork away any ASIC | Vega 56 era 2017 to 2018, approximate USD 2 to 4 per card | 30 Nov 2019: GPU performance "gets a tiny boost or degrades" (MinerUpdate) | 30 Nov 2019: 75 percent of GPU miners offline (MinerUpdate) | RandomX moved it to CPU on purpose; hashrate 300 MH/s to 800 MH/s in weeks. Qubic's rented-CPU campaign claimed 51 percent on 11 Aug 2025 with a 6-block reorg (The Block); the AFT 2026 paper finds no sustained majority and no reward edge |
|
||||
| Zcash (Equihash 200,9) | 28 Oct 2016 | "Universal accessibility" in mining (forum users' reading of the project) | USD 8 per GTX 1080 Ti, Jul 2017 (WhatToMine via 2Miners) | USD 2.63 per 1080 Ti, 6 May 2018 (2Miners) | Jun to Sep 2018 | Antminer Z9 mini announced 3 May 2018; the company stayed neutral; no fork. ASIC chain since; proof-of-stake migration under discussion (approximate) |
|
||||
| Ravencoin (X16R, KAWPOW) | 3 Jan 2018 | GPU-only by design; three hash changes to keep it so | Approximate USD 6 to 8 per 3080, Feb to Apr 2021 (RVN peak USD 0.2857) | 3080 loses USD 0.13 to 0.31 a day, 2026 (WhatToMine) | Gradual through 2023 to 2025 | KAWPOW (6 May 2020) cut hashrate 10x as FPGAs left and held off chips for six years; the Merge doubled it (8.9 to 15.9 TH/s). On 7 Aug 2026 a header-validation flaw let blocks carry "no genuine ProgPoW work" and split the chain; RVN -19 percent (crypto.news) |
|
||||
| Ergo (Autolykos v2) | 1 Jul 2019 (approximate) | Memory-hard, ASIC-resistant, GPU-only (minerstat FAQ) | Approximate USD 3 to 4 per 3080, Sep to Nov 2021 (ERG near USD 15) | USD 0.22 per 3080 before power, Oct 2026 (hashrate.no) | Late 2022 onward | The Merge pushed it from 22 to 155 TH/s in a day (Decrypt); difficulty ate the income within weeks. Still GPU, no chip, income under power |
|
||||
| Kaspa (kHeavyHash) | 7 Nov 2021 | "GPU PoW", fair launch, no premine (bitcointalk ANN title) | Approximate USD 2 to 3 per 3080, Feb to Mar 2023 | 3070 at USD 1.21 a day, 14 Apr 2023 (2Miners) while a KS2 made USD 361 | Apr to Oct 2023 | IceRiver KS0/KS1/KS2 (Apr to Jul 2023), Bitmain KS3 (May 2023). KS0 earned USD 25 a day in Jul 2023. Today 347 PH/s (hashrate.no), KS7 at USD 1,899, GPUs effectively zero |
|
||||
| Grin (Cuckaroo29, Cuckatoo31+) | 15 Jan 2019 | 90 percent of blocks for the GPU hash at launch, falling to zero over two years (Grin forum) | Approximate USD 1 to 2 per card, Jan 2019 | 2020 | 2019 to 2020, by schedule | The schedule itself. Today Cuckatoo32 ASIC only, GRIN at USD 0.035, USD 16 k daily volume (minerstat, approximate) |
|
||||
| Beam (BeamHash III) | 3 Jan 2019 | GPU-friendly Equihash 150,5 | Approximate USD 1 to 2 per card, Jan 2019 | 2020 onward | Most by 2021 | Never an ASIC; the price went. Still GPU-mined, sixth hard fork July 2026, negligible income |
|
||||
| Conflux (Octopus) | Oct 2020 (approximate) | GPU-only, no FPGA or ASIC | Approximate USD 2 to 3 per 3080, 2021 | USD 0.71 per 3080 before power, Oct 2026 (hashrate.no) | Ongoing | Still GPU at 3.9 TH/s. No chip in six years; income under power for most cards |
|
||||
| Alephium (Blake3) | Nov 2021 (approximate) | GPU-mineable sharded chain | Approximate USD 1 to 2 per 3080, 2023 | 2024 | May to Oct 2024 | Goldshell AL-Box 360 GH/s at 180 W (May 2024), AL-Box III 1.25 TH/s (Oct 2024). GPUs out in five months; even the ASICs now lose USD 3.44 a day at USD 0.10 (asicminervalue) |
|
||||
| Nexa (NexaPow) | 21 Jun 2022 | CPU first, then GPU, "scale to global capacity" | Top of the GPU tables Jul 2023 (2Miners) | 2024 (approximate) | 2024 (approximate) | Price. Approximate: down over 99 percent from its 2023 high; hashrate a fraction of 2023 |
|
||||
| Aleo (PoSW) | 18 Sep 2024 | Proving as mining on GPUs | USD 13.50 per RTX 4090 at ALEO USD 9, Sep 2024 (PANews via AiCoin) | Under USD 5 per 4090 at USD 3.40, late 2024 (same source) | Q4 2024 to 2025 | Price and allocation: a one-year lock on early rewards, validators seeded with 270 k to 1.1 M tokens, 10 M stake to validate; f2pool holds 28.9 percent of proving (hashrate.no). "Difficulty plummeted ... as miners exited" |
|
||||
| Iron Fish (Blake3, FishHash) | 20 Apr 2023 | GPU mining of a private chain | Approximate USD 1 to 2 per 3080, Apr 2023 | USD 0.48 per 3080 before power, Oct 2026 (hashrate.no) | 2023 to 2024 | Blocks of "unknown origin and hash power" within weeks (FPGA suspected); FishHash fork 2 Apr 2024 kept it GPU; the price did the rest |
|
||||
| Flux (ZelHash) | 2018 as Zel | GPU-only Equihash 125,4 | USD 50 k a day across the chain, Sep 2022 (2Miners) | Late 2025 | Late 2025 | Proof of Useful Work v2 (RunOnFlux, 23 Oct 2025): FluxNodes produce all blocks; "GPUs stopped having any relevance to FLUX rewards" |
|
||||
| Firo (MTP, FiroPoW) | 2016 as Zcoin | GPU-only ProgPoW variant since 26 Oct 2021 | 4x hashrate after the Merge | USD 0.56 per 3080 before power, Oct 2026 (hashrate.no) | Ongoing | No chip in five years. Still GPU, 13 to 54 GH/s, small |
|
||||
| Vertcoin (Lyra2RE, Verthash) | 8 Jan 2014 | "The people's coin", fork away every ASIC | Approximate USD 1 to 2 per card, Dec 2017 | 2019 | 2018 to 2019 | Two rental 51 percent attacks (Dec 2018: 300-block reorg, about USD 100 k; 1 Dec 2019: 603 blocks replaced, NiceHash). Verthash (Jan 2021) kept GPUs; nobody came back |
|
||||
| Bitcoin Gold (Equihash-BTG) | Oct 2017 | "Make Bitcoin decentralized again" on GPUs | USD 474 a coin, Dec 2017 | 2018 | 2018 to 2020 | 51 percent attacks: May 2018 (388,000 BTG, up to USD 18 M), Jan 2020 (29 blocks, USD 70 k). Delisted by Binance 1 Sep 2024 and Upbit 23 Jan 2025; "under 100 actively reachable nodes" (Decrypt) |
|
||||
| EthereumPoW (Ethash) | 16 Sep 2022 | Ethereum mining continues | Above USD 140 a coin at launch | 2022 to 2023; profitability -85 percent from peak | Within months | Price: USD 1.70 by mid 2025 (99 percent down); pools left for ETC |
|
||||
| Nervos CKB (Eaglesong) | 16 Nov 2019 | GPU-mineable launch | 300 GTX 1070 Ti earned USD 410 a day, 19 Nov 2019 (2Miners) | The same rig earned USD 4.46 a day, 21 Mar 2020 | Mar 2020 | Hashrate 198 TH/s to 1.99 PH/s in 16 days before any chip was sold (secret ASICs); Antminer K5 Apr 2020 at USD 22 then 11 a day |
|
||||
| Radiant (SHA512/256d) | 2022 (approximate) | "GPU/ASIC mining" in the ANN title | Approximate USD 1 per 3080, 2023 | Sep 2024 | Sep 2024 | IceRiver RX0 and DragonBall A11 ASICs, Sep 2024. Designed to go to ASIC |
|
||||
| Karlsen, Pyrin, Nomura (kHeavyHash forks) | Nov to Dec 2023 | "We will ensure long-term GPU-friendly mining" (Karlsen README) | Approximate USD 0.5 to 1 per 3080, Dec 2023 | 2024 | 2024 | Karlsen moved to FishHash (KarlsenHashv2, 12 Sep 2024) as kHeavyHash chips arrived; Pyrin was called "another pre-mine scam" on bitcointalk (five days of private mining before the announced start) |
|
||||
| Dynex (DynexSolve) | Sep 2022 | Proof of useful work, "the most GPU mined coin" (22 percent of Hive OS, Oct 2023) | Approximate USD 2 to 4 per 3080, mid 2023 | 2024 | 2024 | Price: 99.9 percent below its USD 1.39 high (CoinGecko). The useful-work marketplace never paid the miners |
|
||||
| Qubic (uPoW) | 2022 (approximate) | AI training as mining, CPU first | By 2025 about 90 percent of its compute was GPU rigs (Qubic blog) | n/a | Rotating | Pivoted to CPU incentives, then rented its idle fleet to Monero (May to Aug 2025, 27,000 blocks, USD 3.5 M), then to Dogecoin ASIC mining (1 Apr 2026). A hashrate broker, not a GPU chain |
|
||||
| Clore (Kawpow) | 2022 | GPU coin paid for GPU rental | ATH USD 0.45, 17 Mar 2024 | Dec 2025 | Dec 2025 | Proof of work "ended" at block 1,584,180 (Clore changelog, Dec 2025): "miner sell pressure fully removed". A 4090 rents for USD 1.18 to 2.45 a day there |
|
||||
| TurtleCoin (CryptoNight Turtle) | Dec 2017 | A friendly CPU/GPU coin | Negligible | Mar 2023 | 15 Mar 2023 | Halted at block 5,500,000; token moved to Fantom (TurtleCoin v2 FAQ). Dead as a chain |
|
||||
| Musicoin (Ethash) | 2017 | Ethash side income | Negligible | 2019 | Jul 2019 | Repeated 51 percent attacks, Bittrex delisting, 2Miners delisting (31 Jul 2019: "Musicoin is dead") |
|
||||
| Akroma (Ethash) | 2018 | Ethash side income with masternodes | Negligible | 2019 | Jul 2019 | Developers left; USD 92 daily volume; delisted by 2Miners |
|
||||
| Callisto (Ethash) | 2018 | Ethereum Classic airdrop chain | Negligible | 2024 | 2024 | Team liquidity crisis and "civil war" in 2024 (Callisto history page). Dead |
|
||||
|
||||
### 1a. The Merge week, the one migration with hour-level data
|
||||
|
||||
| When (UK) | What happened | Source |
|
||||
|---|---|---|
|
||||
| 15 Sep 2022, 07:43 | The Merge; Ethereum's last proof-of-work block; about 900 TH/s of GPU hash loses its income | Tom's Hardware |
|
||||
| 15 Sep 2022, 07:00 to 14:00 | ETC from 70.5 to 158.3 TH/s; RVN from 8.9 to 15.9 TH/s; ERG from 22 to about 70 TH/s | CoinDesk, Decrypt |
|
||||
| 15 to 16 Sep 2022 | ETC peaks at 296 to 312 TH/s (about a third of Ethereum's hash); ERG peaks at 155 TH/s (7x) | AMBCrypto, crypto.news, Decrypt |
|
||||
| 16 Sep 2022 | ETHW mainnet; the fork trades above USD 140 and falls for three years | KuCoin price history |
|
||||
| 19 Sep 2022 | ETC hashrate down 48 percent from its peak in four days | AMBCrypto |
|
||||
| Sep 2022 | Hiveon pool: 16 percent of its miners to ETC, 7 percent to RVN, the rest spread or gone | Hiveon |
|
||||
| 12 Sep 2022 (before) | 2Miners' arithmetic: USD 24 M a day of GPU income, 94 percent of it Ethereum; USD 1.2 M a day left for all GPU coins after | 2Miners, The Block |
|
||||
|
||||
The arithmetic is the whole story: the receiving chains had one twentieth of the income and received, for a day, one third of the hash. Difficulty did the rest. The 2Miners advice of the day was to pick ETC, RVN or ERG "at least for the first days", and the first days were all there were.
|
||||
|
||||
### 1b. Promised against delivered
|
||||
|
||||
| Chain | The launch text (quoted or paraphrased) | What GPU miners got | Months of GPU income |
|
||||
|---|---|---|---|
|
||||
| Grin | 90 percent of blocks to the GPU hash at launch, falling to zero over two years (forum, Aug 2018) | Exactly that | About 18 |
|
||||
| Ravencoin | GPU-only, fork away FPGAs (KAWPOW, May 2020) | GPU-only for six years; a header bug, not a chip, split the chain in 2026 | 100 and counting, under power since about 2023 |
|
||||
| Kaspa | "GPU PoW", fair launch (bitcointalk ANN title) | 17 months, then chips at 20x and more per joule | 17 |
|
||||
| Karlsen | "We will ensure long-term GPU-friendly mining" (README) | A second hash in nine months to keep the promise; income under power either way | 10 |
|
||||
| Iron Fish | A GPU-mined private chain | Unknown hardware inside weeks; a GPU hash a year later | 12, then under power |
|
||||
| Aleo | Proving as mining on GPUs | USD 13.50 a day per 4090 for a few weeks, then price and pools | 3 |
|
||||
| Flux | GPU-only, "resistance to concentrated specialized-hardware advantages" | Seven years, then block production moved to nodes | 84, then closed |
|
||||
| Clore | Mine the coin that pays for GPU rental | Proof of work ended in December 2025; rental stayed | 36 |
|
||||
| Nervos | GPU-mineable launch | 16 days of 10x hashrate growth from secret chips four months in | 4 |
|
||||
|
||||
What the table says, chain by chain, in one short paragraph each.
|
||||
|
||||
**Litecoin.** The first GPU coin and the first proof that a memory-hard hash on paper is a compute-bound hash in a fab. Scrypt with a 128 kB scratchpad fit on a chip in 27 months. GPU miners did not vote or fork; they sold the cards. Lesson for Igneum: the dataset has to be bigger than any die the chip can afford, and the lane's own history (asic-resistance-history.md rows Scrypt) already prices this.
|
||||
|
||||
**Dogecoin.** The only chain on the list whose GPU exit was a decision, not a defeat. Merged mining handed security to Litecoin's ASICs and the hashrate rose 15x in a month. Lesson: a small chain that shares hardware with a big one is mined by the big one's miners whenever the big one's miners choose; Igneum's hash has no big sibling, which is a feature and a cost.
|
||||
|
||||
**Ethereum.** Seven years of GPU income, three cycles, one exit that was announced for five years and still removed 94 percent of all GPU mining income in one block. The money went to ETC, RVN and ERG for days and then to the shelf. Lesson: miners follow income, not loyalty, and an exit that is scheduled is still an exit.
|
||||
|
||||
**Ethereum Classic.** The refugee chain. It absorbed a quarter of Ethereum's hashrate within hours and lost half of that within four days, then went to ASICs. Lesson: a chain that receives a migration gets a difficulty shock, not a community.
|
||||
|
||||
**Monero.** Four hash forks in 20 months kept chips off and cost 85 percent of hashrate each time (asic-resistance-history.md). RandomX then chose CPUs on purpose and three quarters of the GPU miners left in a day. In 2025 a rented CPU fleet (Qubic) claimed a majority and produced a 6-block reorg; the peer-reviewed analysis finds no sustained majority and no profit. Lesson: ASIC resistance by hand is a treadmill; a majority that cannot profit still frightens exchanges.
|
||||
|
||||
**Zcash.** The clearest record of a project refusing to fork for its GPU miners. The forum poll ran in February 2018, the Z9 shipped in May, the company stayed neutral, the GPUs left by autumn. Income per 1080 Ti fell from USD 8 to USD 2.63 before the chip arrived, because the price fell first. Lesson: the price kills before the chip does.
|
||||
|
||||
**Ravencoin.** The one chain that forked three times and kept the GPUs for six years. KAWPOW cut hashrate 10x on fork day because the FPGAs left. Then in August 2026 a validation bug (not a chip) split the chain. Lesson: random-program hashes work; the client is the next weak point, and Igneum has one client.
|
||||
|
||||
**Ergo.** A GPU-only hash that never saw a chip and still pays USD 0.22 a day per 3080. The Merge gave it 7x the hashrate in a day and the difficulty took the income back in weeks. Lesson: the hash is not the income.
|
||||
|
||||
**Kaspa.** The template Igneum forked. A fair launch, a compute-bound hash, 17 months of GPU income, then IceRiver and Bitmain in the same quarter. In April 2023 a 3070 made USD 1.21 a day beside a KS2 making USD 361. The GPU share went to nothing by the end of the year while the chain prospered. Lesson: the chain can win while its GPU miners lose; Igneum's whole bet is that these two must not separate.
|
||||
|
||||
**Grin and Beam.** Both launched in January 2019 into a bear market. Grin scheduled its own GPU lane to die over two years and it did. Beam never had a chip and still lost its miners to the price. Lesson: a launch into a falling market with a thin order book does not get a second chance; the ramp must survive a bad first year.
|
||||
|
||||
**Conflux, Firo, Iron Fish.** Three GPU-only hashes with no chip after five to six years, each paying under a dollar a day per card. Lesson: resisting the chip buys the right to be small.
|
||||
|
||||
**Alephium.** A sharded chain with a plain Blake3 hash, GPU-mined for about 30 months, then Goldshell shipped a 180 W box in May 2024 and a 1.25 TH/s box five months later. The GPUs were out by autumn and by 2026 the boxes themselves lose money at USD 0.10. Lesson: a chip on a small chain eats the GPUs and then eats itself; the chain gets neither back.
|
||||
|
||||
**Nervos.** The extreme case of private hardware. Hashrate rose 10x in 16 days in March 2020, four months after launch and a month before the first ASIC was on sale; a 300-card GPU farm went from USD 410 a day to USD 4.46. Lesson: the first ASIC is always private, and the launch ramp has to assume one.
|
||||
|
||||
**Radiant.** Said "GPU/ASIC" in its own announcement title and went to ASIC on schedule in September 2024. Lesson: a chain that invites the chip gets the chip; honest, and not Igneum's path.
|
||||
|
||||
**EthereumPoW.** The fork that promised Ethereum mining would continue. The coin traded above USD 140 for a day and under USD 2 by 2025; the pools left for Ethereum Classic within months. Lesson: a fork keeps the code and loses the market; the market is what paid the miners.
|
||||
|
||||
**Karlsen, Pyrin, Nomura.** Three kHeavyHash forks launched into the gap the Kaspa chips opened. Karlsen kept its README promise by changing its hash within a year; Pyrin was called a premine scam on launch day; Nomura is a title. Lesson: "the GPU version of X" is a business model with a nine-month half-life.
|
||||
|
||||
**Nexa.** Fair launch, CPU first, the top of the profitability tables for a summer, then price. Lesson: being the most profitable coin on WhatToMine for a month is a sell signal, not a community.
|
||||
|
||||
**Aleo.** The one chain where mining was proving. The fastest provers took the reward, the pools took the rest, the price fell from USD 9 to USD 3.40 and the GPUs left; the record also shows what miners say when insiders hold the supply. Lesson: Igneum's separation of lottery and proving is the right answer to the first half; the allocation half is answered by having no allocation.
|
||||
|
||||
**Flux, Clore, Dynex, Qubic.** Four projects that stopped paying GPUs for hashing and told miners to rent, host or train instead. Flux moved block production to nodes, Clore ended proof of work and pays for rental, Dynex's marketplace never paid, Qubic became a hashrate broker for Monero and then Dogecoin. Lesson: "useful work" projects abandon the miner the day the useful work does not pay; Igneum's proving must be priced to clear (Horizon rank 7) or it is Dynex.
|
||||
|
||||
**Vertcoin, Bitcoin Gold, Musicoin.** The three small chains 51 percent attacked by rented hash, between USD 70 k and USD 18 M a time. Each was GPU-only and each was attacked because it was. Lesson: a GPU chain is rentable by definition; finality has to be something hash cannot buy.
|
||||
|
||||
**EthereumPoW, TurtleCoin, Akroma, Callisto.** Dead by price, by halt, by abandonment, by a team's money running out. Lesson: a chain with no market is a chain with no miners within a year.
|
||||
|
||||
## 2. What GPU miners say they want
|
||||
|
||||
Twenty posts and articles read in full or in snippet, 2018 to 2026. Each quote is under 15 words. Reddit threads could not be fetched from this environment, so the miner voice here comes from project forums, pool blogs, bitcointalk snippets and press that quoted miners by name.
|
||||
|
||||
| # | Date | Forum or source | Quote | URL |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 25 Feb 2018 | Zcash forum, user vgm | "Centralized ASIC mining will destroy hobby mining along with any organic user growth potential." | forum.zcashcommunity.com/t/lets-talk-about-asic-mining/27353 |
|
||||
| 2 | 25 Feb 2018 | Zcash forum, user JKDC | "Decentralization is as important as privacy." | same thread |
|
||||
| 3 | 26 Feb 2018 | Zcash forum, user aoeu | "I'd rather have GPUs for general computation, not silicon trash." | same thread |
|
||||
| 4 | 25 Feb 2018 | Zcash forum, user Autotunafish | "Zcash is about universal accessibility, not a select few" | same thread |
|
||||
| 5 | 27 Aug 2018 | Grin forum, igno.peverell (launch text) | "A healthy and grassroot GPU mining community at launch is highly desirable." | forum.grin.mw/t/proof-of-work-update/713 |
|
||||
| 6 | 30 Nov 2019 | MinerUpdate (RandomX day) | "Honest miners will be fighting an uphill battle against botnets" | minerupdate.com (section 6) |
|
||||
| 7 | 21 Mar 2020 | 2Miners blog (Nervos) | "ASICs or FPGA's are to blame for a sudden increase in the hash rate." | 2miners.com/blog/nervos-ckb-network-hashrate-increased-asics-are-the-cause/ |
|
||||
| 8 | 9 Apr 2020 | VoskCoinTalk, user Rocky_Bro | "Those 3000watt beasts just eat slower devices for breakfast." | voskcointalk.com/t/.../172 |
|
||||
| 9 | 12 Sep 2022 | The Block, Ethan Vera (Luxor) | "You have to have sub $0.03 per kilowatt hour and new generation GPUs." | theblock.co (section 6) |
|
||||
| 10 | 12 Sep 2022 | The Block, Mark D'Aria (Bitpro) | "Ethereum is 95% of the total income. Everything else will get crushed." | same article |
|
||||
| 11 | 12 Sep 2022 | 2Miners blog, Confessions of a Miner 2 | "When mining makes you lose the money you stop mining." | 2miners.com/blog/confessions-of-a-miner-2-is-this-the-end-of-gpu-mining/ |
|
||||
| 12 | Sep 2022 | Hiveon pool, year review | "16% moving to ETC, 7% going to RVN" | hiveon.com/news/reflection-and-forecast-... |
|
||||
| 13 | 2023 (approximate) | bitcointalk, "Kaspa asics" thread (snippet) | "Specialized mining equipment optimized for corporate industry raping" | bitcointalk.org/index.php?topic=5449022.0 |
|
||||
| 14 | Dec 2023 | bitcointalk, Pyrin ANN thread (snippet) | "another pre-mine scam" | bitcointalk.org/index.php?topic=5476198.0 |
|
||||
| 15 | 2023 | bitcointalk thread title | "Why most premined coins (>90% of all altcoins) will eventually fail" | bitcointalk.org/index.php?topic=5457833.0 |
|
||||
| 16 | 14 May 2024 | Iron Fish blog (on its own launch) | "unknown origin and hash power" | ironfish.network/learn/blog/2024-05-14-fish-hash-audit |
|
||||
| 17 | 2024 | Karlsen README (launch text) | "We will ensure long-term GPU-friendly mining." | github.com/karlsen-network/karlsend |
|
||||
| 18 | Sep 2024 | PANews via AiCoin, Aleo community user | "When you don't know where the liquidity comes from, you are the liquidity." | aicoin.com/en/article/419859 |
|
||||
| 19 | 31 Jul 2019 | 2Miners blog (delisting) | "Musicoin is dead, Akroma is irrelevant." | 2miners.com/blog/akroma-and-musicoin-delisting/ |
|
||||
| 20 | 10 Feb 2026 | Clore blog | "losing money mining with most mid-range GPUs at average electricity rates" | blog.clore.ai/gpu-mining-is-dead-... |
|
||||
|
||||
### The three wants, with counts
|
||||
|
||||
| Want | Posts asking for it (of 20) | Evidence rows |
|
||||
|---|---|---|
|
||||
| 1. Hardware that stays useful: no chip, no FPGA, a card that games or computes when the coin dies | 10 | 1, 2, 3, 4, 5, 7, 8, 13, 16, 17 |
|
||||
| 2. Income above electricity that does not fall off a cliff | 6 | 9, 10, 11, 12, 19, 20 |
|
||||
| 3. A fair supply: no premine, no insider allocation, a coin somebody will buy | 4 | 14, 15, 18, 19 |
|
||||
|
||||
### The three hates, with counts
|
||||
|
||||
| Hate | Posts (of 20) | Evidence rows |
|
||||
|---|---|---|
|
||||
| A. Secret hardware on the chain before anyone can buy it | 5 | 7, 8, 13, 16, and Nervos (section 1) |
|
||||
| B. Premine, VC allocation, locked airdrops | 3 | 14, 15, 18 |
|
||||
| C. The income cliff and the dead market (an exit, a delisting, a 99 percent drawdown) | 5 | 10, 11, 12, 19, 20 |
|
||||
|
||||
### Mapped to Igneum's design (horizon-2026-10.md section 1 and the litepaper)
|
||||
|
||||
| Want or hate | Status | Mechanism, and the honest gap |
|
||||
|---|---|---|
|
||||
| Want 1, hardware stays useful | Partly answered | Random-program hash with hourly swaps, 256 MB dataset with 8 dependent reads, era draws from chain state, no scheduled fork (CLAUDE.md design paragraph). The chip model says 2.1x per joule at class v4 and the N ladder is a decision owed (Horizon item 4 and rank 10). Kaspa's chip was 20x to 1,200x; 2.1x is a different world, but a 2.1x chip at scale still ends home mining on power price alone. The gap is the ladder decision and a public chip model per era |
|
||||
| Want 2, income without a cliff | Partly answered | 100 IGN a block gliding 2.9 percent a month, no halving day, a 1 percent tail (tail-emission.md). No chain on this list ever had a cliff-free schedule, so this is new. The gap: every exit in section 1 came from price, not schedule, and no protocol sets price. What Igneum can do is publish the per-tier income at three prices before launch (the consequences rule) so nobody buys a card on a number that was never promised |
|
||||
| Want 3, fair supply | Answered | No premine, no dev fund, no fee to any team, 90-day ramp from 10 percent (CLAUDE.md, tail-emission.md). Aleo and Pyrin are the counter-examples and Igneum has nothing they had |
|
||||
| Hate A, secret hardware at launch | Partly answered | The ramp from 10 percent makes the first 90 days worth less to a private farm; the correlated-group detector and `security_alert` (Horizon rank 16) show it; the hourly program swap means a private FPGA bitstream has to be re-made every hour. Not answered: a private rented GPU fleet is not hardware and is allowed; Nervos's 16-day 10x rise would look the same on Igneum's charts. The gap is a published launch-week hash-origin report (pools, key counts, fleet shares) so the community sees it the day it happens |
|
||||
| Hate B, premine and allocations | Answered | As Want 3 |
|
||||
| Hate C, the cliff and the dead market | Not answered by protocol | The glide removes the schedule cliff and the proving market gives the coin a buyer other than an exchange (Horizon 4.1a), but a rollup customer paying in dollars is a contract not yet signed (Taiko is the first named customer). Until a customer pays, Igneum has the same exposure as Ergo in 2023: a good hash, a thin market |
|
||||
|
||||
One more thing the posts do not say and the record does: nobody on these forums asked for finality, for a DAG, or for proving. They asked for a card that keeps its value, a bill that gets paid, and a coin that sells. Igneum's technical answers are to questions miners did not ask; the mission should put the three wants on the front page and the finality on page two.
|
||||
|
||||
## 3. The earnings curve as a pattern
|
||||
|
||||
USD per card per day before electricity. A 3080 draws about 220 W, so electricity at USD 0.10 per kWh is USD 0.53 a day; a 4090 at 400 W is USD 0.96. Where a figure is approximate it says so.
|
||||
|
||||
| Chain | Launch | Peak month | Peak income (per 100 MH Ethash or per 3080 equivalent) | Month income fell under power at USD 0.10 | Hashrate that left in the next 90 days |
|
||||
|---|---|---|---|---|---|
|
||||
| Ethereum | Jul 2015 | Jan 2018, then May 2021 | USD 23 per 100 MH (Jan 2018, USD 0.233 per MH); approximate USD 12 to 15 per 3080 in May 2021 | Never for a 3080 before the Merge (approximate USD 1 to 2 a day in Sep 2022, over power); the lane closed 15 Sep 2022 | 100 percent of Ethereum's 900 TH/s; of the GPUs, about a quarter tried ETC and half of those left inside four days (CoinDesk, AMBCrypto); approximate 80 percent of the cards were off within 90 days |
|
||||
| Kaspa | Nov 2021 | Feb to Mar 2023 (approximate) | Approximate USD 2 to 3 per 3080 | Approximate Sep to Oct 2023 (a 3070 was at USD 1.21 in Apr 2023 as the first ASICs landed) | GPU share from near 100 percent to under 10 percent by Dec 2023 (approximate); the network's total hashrate rose the whole time |
|
||||
| Ergo | Jul 2019 | Sep to Nov 2021 | Approximate USD 3 to 4 per 3080 | Approximate Nov to Dec 2022 (the Merge took hashrate from 22 to 155 TH/s in a day) | Approximate 50 to 70 percent of the post-Merge peak within 90 days |
|
||||
| Ravencoin | Jan 2018 | Feb to Apr 2021 | Approximate USD 6 to 8 per 3080 | Approximate Nov 2022 (the FTX month); a 3080 now loses USD 0.13 to 0.31 a day | Approximate 40 percent of the post-Merge peak (15.9 TH/s) within 90 days |
|
||||
| Aleo | Sep 2024 | Sep 2024 | USD 13.50 per 4090 at USD 9 (PANews) | Approximate Q1 2025 as the price reached USD 3.40 and difficulty followed the pools | "Difficulty plummeted ... as miners exited" (PANews); approximate over 50 percent of GPU provers within 90 days of the token-economics reveal |
|
||||
|
||||
What the curve means per tier (the consequences rule), using the record's ratios rather than a price nobody can promise:
|
||||
|
||||
| Tier | What the record says happens to them | What Igneum should tell them before launch |
|
||||
|---|---|---|
|
||||
| Home miner, one 8 or 12 GB card | First under power in every exit (the 3060 lost money on Clore's 2026 table while the 4090 still cleared) | Income per card at three prices, and that the glide never halves it overnight; the card keeps gaming when the income goes |
|
||||
| Home miner, one 16 to 32 GB card | Last GPU standing; still under power 6 to 18 months after a peak on every chain in the table | The proving pool is the second income for this tier only; say so, with the measured 15.6 GB profile |
|
||||
| Rig (6 to 12 cards) | Followed income across chains inside days (Hiveon: 16 percent to ETC, 7 percent to RVN) and shut down inside 90 days | Rig income at three prices; no loyalty expected, none needed, the 30-day weight pays staying miners a vote |
|
||||
| Pool user | Took the pool's choice of chain and the pool's fee; pools held 29 percent of Aleo proving at launch | Which pools carry the finality signer, and that a pool's vote is the user's weight (ledger G8) |
|
||||
|
||||
The common shape, in numbers: income peaks inside one to six months of a price run or a migration, halves within 60 to 120 days as difficulty catches the price, and falls under power within six to eighteen months of the peak; when it does, 50 to 100 percent of the GPU hashrate leaves inside 90 days. The trigger was a chip twice (Kaspa, and ETC's second decline), a scheduled exit once (Ethereum), and the price three times (Ergo, Ravencoin, Aleo). In no case did a GPU lane recover its peak income after the exit.
|
||||
|
||||
## 4. Verdict table
|
||||
|
||||
| # | Lesson | Evidence (chain, year) | What Igneum does today | Gap | What the mission should add or change |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | A compute-bound hash goes to a chip in 16 to 37 months, and the first chip is private | Litecoin 2014, Zcash 2018, Nervos 2020, Kaspa 2023, Alephium 2024 | Random-program hash, 256 MB dataset, latency shadow, era draws; chip model 2.1x at class v4 (Horizon item 4) | Small | Decide the N ladder at genesis and publish the chip model with every era draw; nothing else |
|
||||
| 2 | Secret hardware or fleets show up as an unexplained hashrate step in the first weeks | Nervos 2020 (10x in 16 days), Iron Fish 2023, Kaspa 2023 | 90-day ramp from 10 percent; correlated-group detector and `security_alert` (Horizon rank 16) | Small | A public launch-week hash-origin report (key counts, pool shares, fleet shares) from the observer, daily for the first 90 days |
|
||||
| 3 | Hash forks by hand lose miners and add bugs; the client is the next failure | Monero 2018 to 2019 (85 percent gone per fork), Vertcoin 2018 to 2019, Ravencoin Aug 2026 (header flaw) | No scheduled human fork; era draws from chain state; 95 percent signalling with a floor; Devnet 2 gate | Large on the client | The second independent client is the item; until then a fuzz gate on header and PoW validation paths equal to the sync-request fuzz (Horizon rank 9) |
|
||||
| 4 | Income cliffs empty a chain in days; miners follow income with no loyalty | Ethereum Sep 2022 (94 percent of income gone), ETC -48 percent in four days, Aleo Q4 2024 | Monthly glide, no halving day, 1 percent tail (tail-emission.md) | Small on schedule, large on price | Publish per-tier income at three prices (USD 0.005, 0.02, 0.10) in the litepaper before the testnet, per the consequences rule; nothing in protocol |
|
||||
| 5 | Premine and insider allocation drive GPU miners out faster than any chip | Aleo 2024, Pyrin 2023 | No premine, no dev fund, no fee to any team | None | Nothing |
|
||||
| 6 | A GPU chain is rentable; rented hash reorganised three of them for USD 70 k to USD 18 M | Bitcoin Gold 2018 and 2020, Vertcoin 2018 and 2019, Musicoin 2019 | Miner-only finality on 30 days of blocks; 20 days of 100 percent hash to reach 2/3 (CLAUDE.md) | Small after day 20, large before | Weight-gated deep fork choice (Horizon rank 4) covers the first 20 days; ship it before mainnet, not after |
|
||||
| 7 | A coin with no market dies within a year whatever the hash does | Musicoin 2019, Akroma 2019, TurtleCoin 2023, Grin (USD 16 k daily volume), Nexa | Proving sold in dollars, settled in IGN (litepaper); Taiko named as first customer | Large until a customer pays | A signed proving customer before mainnet is a launch gate, with the same weight as the Devnet 2 gate; decouple the job price from `f_p` (Horizon rank 7) so the first job can clear |
|
||||
| 8 | Useful-work projects drop the miner the day the work does not pay | Dynex 2024, Flux 2025, Clore 2025, Qubic 2025 to 2026 | The lottery pays 80 percent of every block whatever the proving market does; proving is the second income, never the only one | None by design | Write the sentence into the litepaper: "A block pays its miner whether or not anyone buys a proof that day" |
|
||||
| 9 | Random-program hashes keep GPUs for five years and more, and still pay under a dollar a card | Ravencoin 2020 to 2026, Firo, Conflux, Iron Fish | The hash is Igneum's strongest answer | None on the hash | Stop presenting the hash as the reason to mine; present income, fairness and the second income (section 2's three wants) first |
|
||||
|
||||
## 5. Headline findings for the coordinator
|
||||
|
||||
1. Of the 31 rows (33 chains) that paid GPU miners since 2011, 8 lost the GPU lane to a chip, 7 closed it by their own choice (Dogecoin, Ethereum, Monero, Grin, Flux, Clore, Qubic), 10 died or went dormant on price, rental attacks or abandonment, and the 6 still GPU-only (Ravencoin, Ergo, Firo, Conflux, Iron Fish, Beam) pay USD 0.22 to 0.71 a day per RTX 3080 before power, so the hash keeps the miner and not the income.
|
||||
2. In 20 miner posts and launch texts read, 10 asked for hardware that keeps its value, 6 for income above electricity without a cliff and 4 for a fair supply; Igneum answers the fair supply in full, the hardware in part (a 2.1x chip model with the N ladder still owed), and the income only on schedule, since every GPU exit in the record except two came from price.
|
||||
3. The earnings curve has one shape across Ethereum, Kaspa, Ergo, Ravencoin and Aleo: income halves within 60 to 120 days of the peak, falls under USD 0.10 per kWh within 6 to 18 months, and 50 to 100 percent of GPU hashrate leaves inside 90 days of that, so the launch plan needs a signed proving customer and published per-tier income at three prices before the first miner buys a card.
|
||||
|
||||
## 6. Sources (all accessed 7 October 2026 unless noted)
|
||||
|
||||
Project and pool sources
|
||||
- Zcash forum, "Let's talk about ASIC mining", Feb 2018: https://forum.zcashcommunity.com/t/lets-talk-about-asic-mining/27353
|
||||
- Grin forum, "Proof of work update", 27 Aug 2018: https://forum.grin.mw/t/proof-of-work-update/713
|
||||
- 2Miners, Nervos hashrate and ASICs, 21 Mar 2020: https://2miners.com/blog/nervos-ckb-network-hashrate-increased-asics-are-the-cause/
|
||||
- 2Miners, Akroma and Musicoin delisting, 31 Jul 2019: https://2miners.com/blog/akroma-and-musicoin-delisting/
|
||||
- 2Miners, End of Ethereum mining, 30 Aug 2022: https://2miners.com/blog/end-of-the-ethereum-mining-when-what-to-mine-next-with-gpu-transition-to-pos-explained/
|
||||
- 2Miners, Confessions of a Miner 2, 12 Sep 2022: https://2miners.com/blog/confessions-of-a-miner-2-is-this-the-end-of-gpu-mining/
|
||||
- 2Miners, How to mine Kaspa with ASIC and GPU, 14 Apr 2023: https://2miners.com/blog/how-to-mine-kaspa-with-asic-and-gpu-settings-extra-rewards-dual-mining/
|
||||
- 2Miners, July 2023 report (Nexa, Kaspa KS0 port): https://2miners.com/blog/july-2023-work-progress-report-new-coin-nexa-kaspa-geo-servers/
|
||||
- 2Miners, KawPoW fork, May 2020: https://2miners.com/blog/kawpow-new-ravencoin-mining-algorithm/
|
||||
- Hiveon, PoW to PoS notes, 24 Aug 2022: https://hiveon.com/news/the-pow-and-pos-transition-important-notes/
|
||||
- Hiveon, 2022 review (16 percent ETC, 7 percent RVN): https://hiveon.com/news/reflection-and-forecast-reviewing-the-events-of-2022-and-anticipating-the-year-ahead/
|
||||
- VoskCoinTalk, Antminer K5 impressions, 9 Apr 2020: https://voskcointalk.com/t/initial-impressions-of-the-bitmain-antminer-k5-nervos-ckb-eaglesong-asic-miner-mining-22-a-day/172
|
||||
- Iron Fish, FishHash audit and the reason for the switch, 14 May 2024: https://ironfish.network/learn/blog/2024-05-14-fish-hash-audit
|
||||
- Iron Fish, hard fork 1 activation, 2 Apr 2024: https://ironfish.network/learn/blog/2024-02-26-mainnet-hardfork
|
||||
- Karlsen README: https://github.com/karlsen-network/karlsend
|
||||
- hashrate.no, Karlsen and Pyrin algorithm change, 13 Sep 2024: https://www.hashrate.no/c/Algorithm_change_for_Karlsen_and_Pyrin
|
||||
- hashrate.no, RTX 3080 today (top coins and USD per day): https://hashrate.no/gpus/3080
|
||||
- hashrate.no, Kaspa network (347 PH/s): https://www.hashrate.no/coins/KAS
|
||||
- hashrate.no, Aleo pools (f2pool 28.9 percent): https://www.hashrate.no/coins/ALEO/pools
|
||||
- RunOnFlux, Proof of Useful Work v2, 23 Oct 2025: https://runonflux.com/forking-flux-proof-of-useful-work-v2/
|
||||
- Clore changelog (PoW end, Dec 2025): https://clore.ai/changelog
|
||||
- Clore blog, GPU mining is dead, 10 Feb 2026: https://blog.clore.ai/gpu-mining-is-dead-gpu-hosting-is-the-future-heres-how-to-earn-500month/
|
||||
- Qubic blog, mining evolution, 30 Jun 2025: https://qubic.org/blog-detail/qubic-mining-evolution-from-cpu-roots-to-gpu-dominance-and-back-again
|
||||
- Qubic blog, Dogecoin mining on Qubic: https://qubic.org/blog-detail/qubic-dogecoin-mining-how-it-works
|
||||
- TurtleCoin v2 FAQ (halt at block 5,500,000, 15 Mar 2023): https://hackmd.io/@iburnmycd/rJzyWD3-_
|
||||
- Callisto history (2024 collapse): https://www.callisto-pirl.com/history
|
||||
- bitcointalk, Kaspa ANN: https://bitcointalk.org/index.php?topic=5373286.0 (403 on fetch; title and snippet via search)
|
||||
- bitcointalk, "Kaspa asics": https://bitcointalk.org/index.php?topic=5449022.0 (snippet only)
|
||||
- bitcointalk, Pyrin ANN: https://bitcointalk.org/index.php?topic=5476198.0 (snippet only)
|
||||
- bitcointalk, Karlsen ANN: https://bitcointalk.org/index.php?topic=5475216.0 (snippet only)
|
||||
- bitcointalk, "Why most premined coins will eventually fail": https://bitcointalk.org/index.php?topic=5457833.0 (title only)
|
||||
- asicminervalue, Antminer KS7: https://www.asicminervalue.com/miners/bitmain/antminer-ks7-40th
|
||||
- asicminervalue, Goldshell E-AL1M (negative at USD 0.10): https://www.asicminervalue.com/miners/goldshell/e-al1m
|
||||
- asicminervalue, Ethereum Classic: https://www.asicminervalue.com/coins/etc-ethereum-classic
|
||||
- minerstat, Grin: https://minerstat.com/coin/grin_cuckatoo32
|
||||
- WhatToMine, Ravencoin on RTX 3080 LHR: https://whattomine.com/coins/234-rvn-kawpow/gpus/63-nvidia-geforce-rtx-3080-lhr
|
||||
- Binance Research, merged mining case study: https://www.binance.com/en/research/analysis/merged-mining
|
||||
- litecoin.watch, scrypt history: https://litecoin.watch/articles/understanding-the-scrypt-mining-algorithm
|
||||
|
||||
Press and papers
|
||||
- CoinDesk, ETC and RVN hashrate after the Merge, 15 Sep 2022: https://www.coindesk.com/tech/2022/09/15/ethereum-classic-and-ravencoins-hashrate-nearly-doubles-after-merge
|
||||
- AMBCrypto, ETC post-Merge high wearing off, 19 Sep 2022: https://ambcrypto.com/why-ethereum-classics-etc-post-merge-high-is-wearing-off/
|
||||
- crypto.news, ETC hashrate +280 percent: https://crypto.news/ethereum-classic-hashrate-jumped-280-post-merge/
|
||||
- Decrypt, ETC, RVN and ERG hashrate soar, Sep 2022: https://decrypt.co/109803/ethereum-classic-ravencoin-ergo-hash-rate-soar-post-merge
|
||||
- The Block, what is left for GPU miners, 12 Sep 2022: https://www.theblock.co/news/ecosystems/2022-09-12-whats-left-for-ethereum-gpu-miners-after-the-merge-168380
|
||||
- Tom's Hardware, GPU mining unprofitable after the Merge (900 TH/s, about 20 million GPUs): https://www.tomshardware.com/news/gpu-mining-is-now-unprofitable
|
||||
- Wccftech (Minerstat figures, RTX 3080 USD 6.35 to 9.15 a day, early 2021): https://wccftech.com/rgb-lit-bitcoin-mining-rig-78-geforce-rtx-3080-graphics-cards-comes-operational/
|
||||
- FXStreet, Ethereum miners in the red (USD 0.233 per MH per day in Jan 2018), 29 Mar 2018: https://www.fxstreet.com/amp/cryptocurrencies/news/ethereum-price-analysis-eth-usd-eyes-40000-amid-cryptocurrency-sell-off-miners-are-deep-in-red-201803290719
|
||||
- MinerUpdate, CPU mining dominates Monero since RandomX, 30 Nov 2019: https://www.minerupdate.com/news/miner-insights/cpu-mining-dominates-monero-since-randomx-upgrade
|
||||
- Bitcoin Magazine, Antminer Z9 mini, May 2018: https://bitcoinmagazine.com/business/bitmains-antminer-z9-mini-designed-mine-zcash-threatens-asic-resistance
|
||||
- 2Miners, Zcash mining and profitability (1080 Ti at USD 8 in Jul 2017, USD 2.63 on 6 May 2018): https://2miners.com/blog/how-to-mine-zcash-zec-mining-and-profitability/
|
||||
- Medium (Blockwise), the end of GPU mining (Monero April 2018 fork halved hashrate): https://medium.com/blockwise/the-end-of-gpu-mining-a702921fc1e1
|
||||
- crypto.news, Ravencoin consensus flaw, 11 Aug 2026: https://crypto.news/ravencoin-falls-as-consensus-flaw-splits-network/
|
||||
- Decrypt, the long collapse of Bitcoin Gold: https://decrypt.co/47381/the-long-collapse-of-bitcoin-gold
|
||||
- metalicjames gist, Bitcoin Gold Jan 2020 attack: https://gist.github.com/metalicjames/71321570a105940529e709651d0a9765
|
||||
- metalicjames gist, Vertcoin Dec 2018 attack: https://gist.github.com/metalicjames/f2acdb9ef448ec5298173b36c7c54133
|
||||
- news.bitcoin.com, Vertcoin second attack, Dec 2019: https://news.bitcoin.com/vertcoin-network-sabotaged-by-another-51-attack/
|
||||
- Binance Square, BTG delisting 1 Sep 2024: https://www.binance.com/en/square/post/12128321695753
|
||||
- AiCoin (PANews), Aleo mainnet, miners cry foul, Sep 2024: https://www.aicoin.com/en/article/419859
|
||||
- Aleo, mainnet announcement, 18 Sep 2024: https://aleo.org/post/announcing-aleo-mainnet/
|
||||
- The Block, Monero reorg fears after Qubic's 51 percent claim, 12 Aug 2025: https://www.theblock.co/post/366535/monero-faces-chain-reorganization-fears-after-qubic-says-it-controls-51-of-hashrate
|
||||
- Lee and Kim, Inside Qubic's selfish mining campaign on Monero, arXiv 2512.01437, AFT 2026: https://arxiv.org/abs/2512.01437
|
||||
- Decrypt, Qubic mining Dogecoin, Apr 2026: https://decrypt.co/363018/qubic-the-network-that-captured-51-of-monero-is-now-mining-dogecoin-on-its-ai-compute-infrastructure-live
|
||||
- CoinGecko, Dynex (99.9 percent below ATH): https://www.coingecko.com/en/coins/dynex
|
||||
- Medium (crypto-blog), Dynex the most GPU mined coin, Oct 2023: https://medium.com/crypto-blog/dynex-dnx-seems-to-be-the-most-gpu-mined-coin-at-the-moment-cdf7d30c433a
|
||||
- KuCoin, ETHW price history: https://www.kucoin.com/price/ETHW
|
||||
- Kryptex, IceRiver KS series: https://pool.kryptex.com/articles/iceriver-ks0-ks1-ks2-ks3-ks3l-en
|
||||
- Medium (VoskCoin), Was Kaspa mining worth it (KS0 USD 25 a day, Jul 2023): https://medium.com/voskcoin/was-kaspa-mining-worth-it-ca3ac9ff7566 (403 on fetch; figures via search snippet)
|
||||
|
||||
Internal
|
||||
- `docs/analysis/asic-resistance-history.md` (5 Oct 2026)
|
||||
- `docs/analysis/horizon-2026-10.md` section 1 and ranks 4, 7, 9, 10, 16 (6 to 7 Oct 2026)
|
||||
- `docs/analysis/horizon/economy-and-utility.md` section 4.6
|
||||
- `docs/analysis/tail-emission.md`, `docs/analysis/vote-or-burn.md`
|
||||
|
||||
Not reachable from this environment (recorded so the coordinator can fetch by hand): reddit.com (all subreddits), bitcointalk.org thread bodies, web.archive.org, the Springer paper "The PoW Landscape in the Aftermath of The Merge" (login wall), statista's Ethereum profitability series (paywall), bitinfocharts chart values (rendered client-side).
|
||||
446
docs/analysis/mission/reinvent.md
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
# Mission lane 5: what can be reinvented. The miner's experience
|
||||
|
||||
Date: 7 October 2026, UK time. Lane 5 of the last mission. Written in the mission worktree; this file is the only output. Nothing was built, measured, posted or started; the live devnet was not touched.
|
||||
|
||||
the project lead's words that this lane holds: "revolutionise the mining itself"; "dopamine for GPU miners everywhere, a structure that makes the mining commodity fall in love with the project"; "the perfect gpu network that will be noticed as the revolution that everyone is waiting for". Bounds that this lane obeys: no premine, no dev fund, no treasury, no token sale, no airdrop, no referral paid in coins; every coin is mined under the rules that exist (the 80/20 split, the signing bonus of 8 of the 80 producer points, the proving pool); no stake; no device bounty; the three signalling thresholds (60, 90, 95); miners are the security always; gates, not dates; hours are agent hours.
|
||||
|
||||
## 0. Progress
|
||||
|
||||
| Time (UK) | State |
|
||||
|---|---|
|
||||
| 08:46 | Rules read: CLAUDE.md, polish.md sections 1, 3.1, 3.2, 3.4, 3.6, 3.7, 3.10, 4.5; litepaper miner sections; vote-or-burn section 5; tail-emission one page; horizon one page; ledger decisions 3, standing, 6 and 7 October; pool.md; miner-ui-3 and its audit; testnet-go; the dev fee; the Discord kit; the Reddit kit (branch reddit-kit); journey.json |
|
||||
| 09:05 | Three research sub-lanes launched: Reddit post sample, chain histories, pool and install facts. The third refused by the concurrency limit; its topics done by hand with WebFetch. The session's WebSearch budget was already spent by other lanes, so every outside fact below comes from a fetched page or is labelled approximate |
|
||||
| 08:52 | Igneum maths computed (scratch `mission-reinvent/igneum-maths.txt`, `tiers.txt`): solo block expectations per tier at 100 GH/s and 1 TH/s, the vote dust line, the signing bonus per day |
|
||||
| 09:04 | Sections 3 to 8 drafted; 1.1 and 2 waiting on the sub-lanes |
|
||||
| 09:10 | Section 1.1 and headline 3 written from `reddit-sample.md` (105 posts, 69 with a room median) |
|
||||
| 09:13 | Section 2 and the sources written from `chains.md` (seven chains, hashrates derived from explorer APIs at dated heights) |
|
||||
| 09:15 | Done: 0 em dashes by grep, no placeholders, 446 lines; the three findings sent to the coordinator |
|
||||
|
||||
## 1. The dopamine loop of a GPU miner, from data
|
||||
|
||||
### 1.1 What the archives say
|
||||
|
||||
Method (the sub-lane's file, scratch `mission-reinvent/reddit-sample.md`): 105 posts picked by hand from the top-of-all-time listings of r/EtherMining, r/gpumining, r/chia, r/ravencoin, r/MoneroMining, r/Monero, r/HeliumNetwork (2021 to 2022), r/kaspa and r/erg_miners (r/ErgoMining is an empty shell whose top post points to r/erg_miners), plus six bitcointalk "first block" posts from 2011 to 2017 through the ninjastic archive. Each post's score is the value Reddit served on 7 October 2026. The subreddit median for the post's year is estimated from three half-month samples of the Arctic Shift archive (approximate; the typical post in these rooms scores 1 to 4 points, so the ratios are large and describe how far the best joy posts rise above an ordinary post). 69 of the 105 rows have a ratio; r/MoneroMining and r/erg_miners have no archive sample.
|
||||
|
||||
| Post type | Rows with a ratio | Median score | Median ratio to the room's typical post | Range | Best example (score, date, URL) |
|
||||
|---|---|---|---|---|---|
|
||||
| f. "My card paid for itself" (ROI, break-even) | 6 | 777 | 777x | 493x to 1,367x | solar panels paid for by mining, 1,367 points, 16 May 2021, https://www.reddit.com/r/gpumining/comments/ndduuu/ |
|
||||
| g. The community meme | 11 | 710 | 739x | 81x to 2,054x | "New miners be like...", 2,054 points, 19 Apr 2021, https://www.reddit.com/r/EtherMining/comments/mu3jgh/ |
|
||||
| c. The rig photo | 13 | 361 | 417x | 48x to 2,923x | "Rate my new build", 2,923 points, 3 Mar 2021, https://www.reddit.com/r/EtherMining/comments/lwpudz/ |
|
||||
| d. The hashrate milestone | 7 | 324 | 409x | 95x to 672x | "What 1GH looks like", 672 points, 8 Apr 2021, https://www.reddit.com/r/EtherMining/comments/mmybn2/ |
|
||||
| e. The profit screenshot, "X per day" | 7 | 234 | 234x | 48x to 593x | "I finally mined a full Ethereum today", 593 points, 21 Jul 2021, https://www.reddit.com/r/EtherMining/comments/ootl28/ |
|
||||
| a. First block found solo | 8 (plus 6 bitcointalk rows with no score) | 233 | 119x | 46x to 762x | "Tried Solo Mining ETH as an Experiment, Ended up Hitting a Block", 762 points, 3 Jun 2021, https://www.reddit.com/r/EtherMining/comments/nr13be/ |
|
||||
| h. "We did it", the network milestone (ATH, listing, hotspot count, halving) | 12 | 214 | 66x | 12x to 305x | Helium's first telecom deal, 305 points, 26 Oct 2021, https://www.reddit.com/r/HeliumNetwork/comments/qg9o6g/ |
|
||||
| b. First payout | 5 | 173 | 56x | 43x to 478x | "I made my first bitcoin penny", 478 points, 8 Mar 2021, https://www.reddit.com/r/gpumining/comments/m0emml/ |
|
||||
| all | 69 | 256 | 297x | 12x to 2,923x | |
|
||||
|
||||
What the ranking says. The posts that rise highest are about the miner, not the network: the card that paid for itself (777x), the rig in a photo (417x), the personal hashrate milestone (409x). The network's own milestone, the thing a project would post, sits near the bottom (66x). The first solo block is the strongest single feeling in the quotes and a middling score (119x), because it is rare; the first payout is the lowest (56x), because on a pool it is routine. Chia's first-block posts are the exception that proves it: on a chain where the first win took weeks of plotting, "It finally happened! I never thought this day would come" scored 615 (19 May 2021, https://www.reddit.com/r/chia/comments/ngevxy/), 308x its room's median. The Monero room shows the same feeling at every size of CPU: "After 271 days and 101 Billion Hashes, I Finally Mined my First Monero" (128 points, 20 Nov 2020, https://www.reddit.com/r/MoneroMining/comments/jxxwzo/).
|
||||
|
||||
Quotes (verbatim, with the sub-lane's dates and URLs; all accessed 7 October 2026):
|
||||
|
||||
| Quote | Where | Date |
|
||||
|---|---|---|
|
||||
| "GUYZ! I DID IT! I FINALLY WON!!" | r/chia title, https://www.reddit.com/r/chia/comments/myaizh/ | 25 Apr 2021 |
|
||||
| "cries in 17TiB with 0 xch" | top comment on the same post | 25 Apr 2021 |
|
||||
| "380 MH/s, about 24 hours total time mining. Very, very lucky." | r/ravencoin, https://www.reddit.com/r/ravencoin/comments/pnfl4y/ | 13 Sep 2021 |
|
||||
| "That's crazy. Now all of us pool miners are going to think hmm." | r/EtherMining, https://www.reddit.com/r/EtherMining/comments/nr13be/ | 3 Jun 2021 |
|
||||
| "1 GH/s and disappointed parents achieved!" | r/gpumining title, https://www.reddit.com/r/gpumining/comments/okexp4/ | 14 Jul 2021 |
|
||||
| "Love solo mining its the best." | r/MoneroMining, https://www.reddit.com/r/MoneroMining/comments/1uexakh/ | 25 Jun 2026 |
|
||||
| "I just solo mined my first block ever! :3 Feels good." | bitcointalk, https://bitcointalk.org/index.php?topic=619210.msg6866375#msg6866375 | 22 May 2014 |
|
||||
| "Mind blown." | bitcointalk, first reply to "just found my first block", https://bitcointalk.org/index.php?topic=338903.0 | 19 Nov 2013 |
|
||||
|
||||
Consequence for Igneum, by surface: the shareable thing must be the miner's own object (the block card with the card's name on it, the rig's row on the census, the weight rank), never the project's milestone; the ROI post cannot exist without a price, so its stand-in at the testnet is the line "N IGN per kWh" and the days-mined ring; and the solo first block must stay reachable for the common tier, which is the reason the Poisson line and the pool switch sit on the same screen (section 3.2).
|
||||
|
||||
### 1.2 Time to the first hit on each chain
|
||||
|
||||
| Chain, year | First hit | Measured or estimated time from install | Source |
|
||||
|---|---|---|---|
|
||||
| Ethereum 2017, pool (Ethermine, Nanopool) | first share accepted, worker on the dashboard | share within seconds of the first job; worker visible in 1 to 10 minutes; first payout at 0.05 ETH took one GTX 1070 about 2 to 3 weeks in mid 2017 (approximate) | approximate, from memory of the pool pages |
|
||||
| Ethereum 2017, solo | first block | a 30 MH/s card against 60 TH/s: one block in about 2 million blocks, 1 year or more; nobody did it (approximate) | approximate |
|
||||
| Kaspa, launch day 8 Nov 2021, solo | first block | the network was 3.4 MH/s on 8 Nov 2021 (derived from the header bits, `chains.md`), so one 2021 card found blocks within minutes; at 2.86 TH/s six months later the same card needed a pool | https://api.kaspa.org/blocks-from-bluescore?blueScore=100000 (7 Oct 2026) |
|
||||
| Kaspa 2022, pool (2miners) | first share; payout at 50 KAS every 2 hours | share in seconds; payout the same day for a 1 GH/s card in 2022 (approximate); 2miners: "Payouts are processed automatically every 2 hours", minimum 50 KAS | https://2miners.com/faq and https://kas.2miners.com/ (7 Oct 2026) |
|
||||
| Monero 2019, p2pool (from 2021) | a share in the PPLNS window, paid at the next pool block | p2pool: "It can take several days to a week to find a share if your hashrate is low"; min payout 0.00027 XMR | https://p2pool.io/ (7 Oct 2026) |
|
||||
| Chia 2021 | the first plot finished (the hit was the plot, not the coin) | 6 to 12 hours a plot on a consumer SSD; the expected solo win on 10 TB at 5 EiB netspace was months (approximate) | r/chia posts of May 2021 (section 1.1); approximate |
|
||||
| Helium 2021 | the hotspot on the map, the first beacon, "HNT per day" in the app | hours after power-on, days to weeks of waiting for the device itself (the r/HeliumNetwork top post of 2021 is a photo of a miner six months after ordering, section 1.1) | section 1.1 |
|
||||
| Ravencoin 2018, solo | first block, 5,000 RVN | on launch day any card found blocks in minutes (approximate) | approximate |
|
||||
| Igneum devnet, 2026 | first accepted block, shown in the app's event feed | an Apple silicon laptop through the DMG: first block "within the first minutes", 33 accepted blocks in 7 minutes; an RTX 5090 through the Windows installer: 34 accepted blocks in the first minute once the clock was right | `docs/bench-log.md` 4 October 2026 (the first outside machine; the three-card rig) |
|
||||
|
||||
### 1.3 The loop
|
||||
|
||||
| Stage | What it is for a miner | Evidence |
|
||||
|---|---|---|
|
||||
| Trigger | a number moved: a block, a payout, a hashrate tick, a chart on the explorer, a post in the feed | the post types that score highest are all "a number moved and here is the screenshot" (section 1.1) |
|
||||
| Action | open the app, the pool page, the explorer, the Discord; refresh | the daily ritual on every chain in section 2 |
|
||||
| Variable reward | blocks are a Poisson draw; the payout varies; the post's upvotes vary; luck is the word every pool shows | 2miners shows "luck 441%" and "last block 2 minutes ago" on the pool's front page (https://kas.2miners.com/, 7 Oct 2026) |
|
||||
| Investment | a rig built, plots made, a key aged, a reputation in the room, a name on a board | Chia's plots, Helium's named hotspot on the map, the EtherMining rig-photo post (section 1.1) |
|
||||
|
||||
Igneum has one investment no other chain has: the vote key's 30-day window. A key that mined for 30 days weighs more than the same hashrate that appeared yesterday, and that weight signs finality. That is a level system written into consensus, and section 3.7 builds the ladder on it.
|
||||
|
||||
### 1.4 The five moments on Igneum, and the time to each today
|
||||
|
||||
Computed at 1 block a second, the producer share of 80 points, 100 IGN a block at full rate. The network size is the variable: the first 1,000 miners at the measured median card (about 25 MH/s, section 3.8) are about 25 GH/s; 100 GH/s is the litepaper's example; 1 TH/s is where the loop breaks for solo cards. Full tables per tier: section 3.8 and scratch `tiers.txt`.
|
||||
|
||||
| Moment | Solo, 100 MH/s on 100 GH/s | Solo, 17 MH/s (8 GB) on 100 GH/s | Pool member, any card | Where it is shown today | Where it should be shown |
|
||||
|---|---|---|---|---|---|
|
||||
| 1. First share or first block | first block: expected gap 16.7 min, 97 percent within the first hour | 1.6 h expected, 46 percent within the first hour | first share: vardiff starts at one share per 10 s and the first correction is sized from the measured rate (`pool.md` section 2), so under a minute after the first job; before that the node sync and the dataset build (measured: the devnet laptop's first block "within the first minutes") | the event feed and the blocks strip (`app.js:1052-1127`) | the block card of section 3.3 |
|
||||
| 2. First payout | the coinbase of the first block, credited by the execution layer when the block is blue; so the payout is the block, seconds later | the same | PPLNS credit on blue confirmation; a payout round every 60 s (`pool/src/config.rs:61`), minimum 1 IGN; an 8 GB member at 100 GH/s earns 1 IGN in about 73 s, so the first payout lands inside two minutes of the first share | Earnings reads "£0.00 earned" and never shows mined IGN (Q18) | Earnings as section 3.4 |
|
||||
| 3. First proof shard paid | shards are assigned by lot to eight provers for ten seconds; 388 shards and 446.13 IGN paid on 5 October (the Discord kit's start-here text); on a 24 GB NVIDIA card today, 12 GB cards with the patched prover measured not shipped | not available on 8 GB today (core-only proving measured, `prover-tiers-real-cards.md` rows 4060) | proving income never passes through the pool (C6) | Prove tab state line "0 proven · 0 paid" | "your card proved shard N of block M" as a card, section 3.7 |
|
||||
| 4. First vote (the key above dust, 100 blocks in the 30-day window) | 1.2 days | 6.8 days | the member's own blocks carry its key (`pool.md` section 2, Votes), so the same days as solo for the same card; a member without a node has no vote (spec 09 9.7) | nothing: the app never says whether the key votes | "your key has a vote" card, the ladder rung 1 |
|
||||
| 5. First signature in a certified checkpoint, then the full window | the first checkpoint after the key enters the table (one checkpoint is 30 s; a young key counts as present); the full window at day 30 | the same; full window at day 30 | the same | nothing | "your signature is in checkpoint K" and "30 of 30 days" |
|
||||
|
||||
The consequence of the dust line, per tier (the standing rule of 5 October): at 100 GH/s every tier reaches a vote inside a week; at 1 TH/s a single 8 GB card finds 44 blocks in 30 days and never reaches 100, so it never votes, and the pool does not help because weight follows the member's own found blocks. The 12 GB RTX 4070 reaches 65, also under. The RTX 5090 reaches 255. So past about 500 GH/s the "your key has a vote" rung is out of reach for the cards most of the first 1,000 miners own, and the X5 gate (1,000 independent keys above dust) gets harder as the network grows. This lane does not touch the constant; it hands the finality lane the question in section 8 and keeps the ladder's first rungs (first block, first payout, first shard, 30 days mined) reachable for every tier at any network size.
|
||||
|
||||
## 2. The product structures that made miners fall in love
|
||||
|
||||
Method (the sub-lane's file, scratch `mission-reinvent/chains.md`): hashrates derived from block difficulty at dated heights through each chain's public explorer API (Ergo, Monero, Ravencoin, Kaspa) or Etherscan's daily CSV (Ethereum); Helium and Chia from their own HIPs, blog posts and release notes; prices from CoinGecko. Every URL opened on 7 October 2026. "Approximate" marks a figure from memory. Hash units differ across forks and are not compared across them.
|
||||
|
||||
### 2.1 The hook, the ritual, the proof, the ladder, the story
|
||||
|
||||
| Chain | Hook | Daily ritual | Social-proof surface | Status ladder | The story a miner told |
|
||||
|---|---|---|---|---|---|
|
||||
| Helium 2019 to 2021 | a USD 500 box on the windowsill mines by giving radio coverage | the app or explorer.helium.com: the hotspot's 24-hour HNT, witnesses, "relayed" | the explorer hex map; every hotspot a dot with a three-word name; "the map was the product" | none formal: "first in my city" on the map, maker badges in Discord, validators from 2021 (approximate) | HIP 10's own example: a hotspot "which has earned approximately $21,000 worth of HNT by purchasing $145 worth of DC" (https://github.com/helium/HIP/blob/main/0010-usage-based-data-transfer-rewards.md) |
|
||||
| Chia 2021 | farm on drives you already own; "green Bitcoin" from the BitTorrent inventor | the GUI's netspace and "estimated time to win"; plots finishing; then the pool page | the netspace chart (chiaexplorer, xchscan); the pool leaderboard | plot count and TiB; "OG plots" against portable plots (approximate beyond that) | "It finally happened! I never thought this day would come" (section 1.1); the other side: "cries in 17TiB with 0 xch" |
|
||||
| Ethereum 2017 | the gaming PC prints money while you sleep | WhatToMine with your card list, the pool's unpaid balance and graph, the overclock | the pool's worker list and "estimated earnings"; YouTube rig builds; the GPU price chart | rig size (6, 8, 12 cards), hashrate, the "paid off" post (approximate) | "In Feb this subreddit told me it was too late and I'd never ROI" (893 points, 1 Aug 2021, https://www.reddit.com/r/EtherMining/comments/ovvaah/) |
|
||||
| Kaspa 2022 | "No premine. No hidden allocation. Fair launch." (https://kaspa.org/) | miningpoolstats.stream, the pool dashboard, Discord #mining | the miningpoolstats hashrate chart and pool count | Discord roles for early miners and pool operators (not verified, approximate) | "I mined six figures of KAS on two GPUs at a tenth of a cent" (approximate, the room's tone) |
|
||||
| Monero RandomX 2019 | any CPU, even a laptop, mines again; RandomX "optimized for CPUs" (https://www.getmonero.org/resources/moneropedia/randomx.html) | the xmrig console, the pool page, the electricity maths | the RandomX README benchmark table (i9-9900K 5,770 H/s, Ryzen 7 1700 4,100, i3-3220 510, Raspberry Pi 3 20; https://github.com/tevador/RandomX) and the pool's miner count | none formal; hashes per watt bragging | "After 271 days and 101 Billion Hashes, I Finally Mined my First Monero" (section 1.1) |
|
||||
| Ravencoin 2018 | Bitcoin's fair launch replayed for GPUs; "no private, public, founder, or developer allocation" (https://ravencoin.org/assets/documents/Ravencoin.pdf) | the pool dashboard, the WhatToMine line, Discord #mining, "is that hashrate jump an FPGA?" | the pool ranking and the explorer difficulty chart | none formal | "380 MH/s, about 24 hours total time mining. Very, very lucky." (section 1.1) |
|
||||
| Ergo 2019 | the ETH miners' second home: GPU-only Autolykos, a 2 GB table, "hardcoded no-premine proof" (https://github.com/ergoplatform/ergo/releases/tag/v3.0.0) | the pool dashboard, the explorer, WhatToMine, Discord #mining | the hashrate chart and the pool list | none formal | "My first payout. All mined with my humble GTX 1060 at 50MH/s." (section 1.1) |
|
||||
|
||||
What the seven have in common: the daily ritual is a number read off a page the project does not control (a pool, miningpoolstats, WhatToMine); the social proof is a chart or a map; the ladder is informal everywhere. Helium is the one chain whose product was the proof surface itself (the map), and it is the one whose miners posted photos of the device six months after ordering (section 1.1). No chain had a ladder the chain itself computed.
|
||||
|
||||
### 2.2 The numbers at 3, 6 and 12 months
|
||||
|
||||
| Chain, launch | 3 months | 6 months | 12 months | Peak | Source |
|
||||
|---|---|---|---|---|---|
|
||||
| Helium, 1 Aug 2019 | about 1,500 hotspots (approximate) | about 3,000 (approximate) | about 7,500 (approximate) | "500,000+ Hotspots" (HIP 54, 2 Feb 2022); "close to 1 million" (HIP 70, 30 Aug 2022) | https://raw.githubusercontent.com/helium/HIP/main/0054-h3dex-targeting.md ; https://github.com/helium/HIP/blob/main/0070-scaling-helium.md |
|
||||
| Chia, 19 Mar 2021 (transactions 3 May) | "over 30 exabytes" (7 Jul 2021) from "about 100 pebibytes" at launch | 32 EiB (3 Aug 2021) | 28 EiB (19 Mar 2022); "over 400,000 nodes" | about 35 to 40 EiB late 2021 (approximate); 17.3 EiB on 7 Oct 2026 | https://www.chia.net/2021/07/07/official-pooling-protocol-launched/ ; https://www.chia.net/2022/03/19/chia-mainnet-year-one/ ; https://alltheblocks.net/chia |
|
||||
| Ethereum GPU, from 1 Jan 2017 (6.0 TH/s) | 17.3 TH/s (1 Apr 2017) | 37.2 TH/s (1 Jun 2017) | 159.4 TH/s (1 Jan 2018) | 1,126.7 TH/s (13 May 2022); 871.0 TH/s on 14 Sep 2022, zero from 16 Sep | https://etherscan.io/chart/hashrate?output=csv |
|
||||
| Kaspa, 7 Nov 2021 (3.4 MH/s on 8 Nov) | about 1 TH/s (approximate; 0.3 TH/s on 19 Dec 2021) | 2.86 TH/s (28 May 2022) | 431.8 TH/s (26 Nov 2022) | 210.7 PH/s (12 Apr 2024, derived); about 344 PH/s on 7 Oct 2026 | https://api.kaspa.org/blocks-from-bluescore?blueScore=15800001&includeTransactions=false ; https://api.kaspa.org/info/hashrate?stringOnly=false |
|
||||
| Monero RandomX, 30 Nov 2019 (307 MH/s CryptoNightR before, 686 MH/s RandomX on 1 Dec) | 1,338 MH/s (29 Feb 2020) | 1,372 MH/s (30 May 2020) | 1,695 MH/s (29 Nov 2020) | 7.54 GH/s (16 Jan 2026) | https://xmr-node.cakewallet.com:18081/json_rpc (get_block_header_by_height) ; https://www.coinwarz.com/mining/monero/hashrate-chart |
|
||||
| Ravencoin, 3 Jan 2018 | 0.89 TH/s (19 Mar 2018) | 1.31 TH/s (17 Jun 2018) | 3.51 TH/s (7 Jan 2019) | X16R 22.5 TH/s (29 Sep 2019); KAWPOW 18.76 TH/s (20 Sep 2022); 0.61 TH/s on 7 Oct 2026 | https://blockbook.ravencoin.org/api/v2/block/130000 (and later heights) ; https://rvn.2miners.com/api/stats |
|
||||
| Ergo, 1 Jul 2019 | 1.06 TH/s (1 Oct 2019) | 1.29 TH/s (31 Dec 2019) | 7.89 TH/s (30 Jun 2020) | 180.6 TH/s (17 Sep 2022); 0.47 TH/s on 7 Oct 2026 | https://api.ergoplatform.com/api/v1/blocks?limit=1&offset=66000&sortBy=height&sortDirection=asc (and other offsets) ; https://erg.2miners.com/api/stats |
|
||||
|
||||
Discord member counts were not found in any source opened for any of the seven; the one community-size number in the sample is r/erg_miners passing 5,000 subscribers on 6 Jan 2022 (section 1.1, row 104).
|
||||
|
||||
What the first year looked like from a card: Kaspa went from 3.4 MH/s on its first day to 2.86 TH/s at six months, a million-fold, and then 150x more in the next six (the post-Merge fleet); Chia's netspace rose about 300x in under four months, so a fixed farm's share fell 300x; Ethereum rose 26x across 2017. A miner who joined on day one of any of them watched their share fall by two or three orders of magnitude inside a year. Igneum's 30-day window gives that miner something the hashrate curve took away: weight that the newcomers cannot buy for 30 days.
|
||||
|
||||
### 2.3 The failure modes
|
||||
|
||||
| Chain | What broke | The number | Source |
|
||||
|---|---|---|---|
|
||||
| Helium, 2022 | dilution then gaming then a pivot: emission halved to 2,500,000 HNT a month (1 Aug 2021, HIP 20) while hotspots went past "close to 1 million", so under 0.1 HNT a day per hotspot against about 5 to 10 in 2020 (approximate); a denylist run by the company from about 14 Jan 2022 whose method is not published; subDAO tokens with a "50B pre-mine" of MOBILE (HIP 53); the move to Solana (April 2023); the SEC complaint of 17 Jan 2025 over claims about Lime, Nestlé and Salesforce, settled for USD 200,000 | HNT ATH USD 54.88 to ATL 0.1132; "almost 38% of all HNT is staked in Validators" (HIP 70) | https://raw.githubusercontent.com/helium/HIP/main/0020-hnt-max-supply.md ; https://github.com/helium/denylist ; https://raw.githubusercontent.com/helium/HIP/main/0053-mobile-dao.md ; https://www.sec.gov/enforcement-litigation/litigation-releases/lr-26229 ; https://www.coingecko.com/en/coins/helium |
|
||||
| Chia, 2021 | the SSD burn (about 1.3 TiB of writes per plot, Chia's own figure; consumer drives killed in weeks, the press figure approximate), the pool protocol eight months after the pools promise (11 Nov 2020 to 8 Jul 2021) with "OG plots" shut out of it, a closed pool at a third of netspace, 300x dilution in four months, then consolidation and a price fall of 99.9 percent | hpool "hovering at around 33% of netspace" (15 Jun 2021); XCH ATH USD 1,645.12 to ATL 1.14 | https://www.chia.net/2021/05/24/chia-and-ssd-endurance/ ; https://www.chia.net/2021/06/15/common-misconceptions-vol-1/ ; https://github.com/Chia-Network/chia-blockchain/releases/tag/1.2.0 ; https://www.coingecko.com/en/coins/chia |
|
||||
| Ethereum, 15 Sep 2022 | the Merge switched 871 TH/s off in a day; ETC absorbed at most 236.6 TH/s; Ergo rose 11x in 48 hours (16.1 to 180.6 TH/s) and Ravencoin 7x (2.70 to 18.76 TH/s), so every existing Ergo rig lost about 91 percent of its share inside a week; both fell back by Christmas (32.6 and 9.19 TH/s) | the bagpipes post: "Powering down last ETH mining rig the only proper way", 2,277 points, 15 Sep 2022, https://reddit.com/r/EtherMining/comments/xek7wq | https://etherscan.io/chart/hashrate?output=csv ; https://bitinfocharts.com/comparison/hashrate-eth-etc.html ; https://decrypt.co/109713/ethereum-classic-hashrate-soars-merge-nears-miners |
|
||||
| Kaspa, 2023 | the ASIC turn: IceRiver KS0 to KS3 listed for "Sep 2023", Bitmain KS3 "Oct 2023"; network hashrate 2.67 PH/s (28 Jul 2023) to 115.7 PH/s (19 Dec 2023), 43x in under five months; one KS1 at 1 TH/s equals about 2,000 RTX 3070-class cards (approximate), so the GPU share went to a rounding error | 43x | https://www.asicminervalue.com/miners/iceriver ; https://api.kaspa.org/blocks-from-bluescore (derived) |
|
||||
| Monero, 2019 onward | nothing broke on chain; two slow failures: botnet mining ("Monero's popularity among malware-based non-consensual miners", Wikipedia), and a 4x hashrate rise to the 2026 peak against the 0.6 XMR tail | 686 MH/s (1 Dec 2019) to 7.54 GH/s (16 Jan 2026) | https://en.wikipedia.org/wiki/Monero ; https://www.coinwarz.com/mining/monero/hashrate-chart |
|
||||
| Ravencoin, 2019 to 2020, then 2022 | the FPGA and ASIC surge: 10.0 to 22.5 TH/s between 22 Aug and 29 Sep 2019 with no price move; two algorithm forks (X16Rv2, 1 Oct 2019, "ASIC shmasic"; KAWPOW, May 2020, hashrate 29.4 to 3.39 TH/s overnight); then the economic break after the Merge, 18.76 TH/s to 0.61 TH/s today, and a "consensus flaw in KAWPOW header validation" rescued by a pool in Aug 2026 | hashrate down 97 percent from the 2022 peak; RVN down 99.2 percent | https://github.com/RavenProject/Ravencoin/releases?page=2 ; https://github.com/RavenProject/Ravencoin/releases/tag/v4.1.0 ; https://2miners.com/blog/august-2026-work-progress-ravencoin-chain-rescue-pearl-hard-fork-and-kaspa-reward-cut/ |
|
||||
| Ergo, 2022 | the overflow pipe: 16.1 TH/s (3 Sep 2022) to 180.6 (17 Sep), back to 36.8 by 3 Oct; 0.47 TH/s today, under 3 percent of the peak | 11x up and 80 percent down in a month | https://api.ergoplatform.com/api/v1/blocks (derived) ; https://erg.2miners.com/api/stats |
|
||||
|
||||
What Igneum takes from each: from Helium, a proof surface the miner owns (the map) and a warning that a company-run denylist with an unpublished method ends the love (section 6, "a number a company server has to be trusted for"); from Chia, that the ritual (plotting) must not destroy the hardware, and that a pool that arrives eight months late with the early miners shut out is remembered (pool-0 is at the go, section 3.5); from Ethereum, that the shareable post is the rig and the ROI, and that a chain can end mining by decree (Igneum's miners are the security always, so there is no Merge to decree); from Kaspa, that a fair launch holds a community for two years and an ASIC takes it in five months (the hourly program and the census, 4.4); from Monero, that "anyone's CPU" is a narrative that survives seven years when the hash keeps its promise; from Ravencoin, that two algorithm forks by human release are what Igneum's automatic schedule replaces; from Ergo, that a hashrate wave arriving from a dead chain dilutes the faithful 11x in two days, which is exactly what the 30-day weight window was built to keep from the vote.
|
||||
|
||||
## 3. The miner's experience on Igneum, reinvented
|
||||
|
||||
### 3.1 Install: the two warnings before the first screen
|
||||
|
||||
What happens today (polish.md 3.10): Windows, SmartScreen "Windows protected your PC" (More info, Run anyway), per-user install, then a UAC prompt for the firewall rule 20 to 50 s in, on top of whatever screen is up. Mac, an ad hoc signature, Gatekeeper refuses, the user must find Privacy and Security, Open Anyway; the app strips its own quarantine on start. The Windows installer cannot be rebuilt tonight (Q80, GitHub billing).
|
||||
|
||||
What the comparators do: SmartScreen warns on any file that is not "well known and downloaded frequently" and on any certificate without reputation; "If a URL, a file, an app, or a certificate has an established reputation, users don't see any warnings" (https://learn.microsoft.com/en-us/windows/security/operating-system-security/virus-and-threat-protection/microsoft-defender-smartscreen/, page dated 23 April 2026, read 7 Oct 2026). SSL.com's code-signing page says Microsoft "moved away from automatic instant reputation", so OV and EV build reputation through use (https://www.ssl.com/code-signing/, 7 Oct 2026). Apple: USD 99 a year, notarisation included (https://developer.apple.com/programs/, 7 Oct 2026); on macOS the unidentified-developer path is System Settings, Privacy and Security, Open Anyway (https://support.apple.com/en-us/102445, 7 Oct 2026). Certum sells an open-source code-signing certificate "from €49.00" (https://shop.certum.eu/open-source-code-signing-on-simplysign.html, 7 Oct 2026; out of stock on the day). Azure Trusted Signing: Basic tier 5,000 signatures a month, one certificate profile of each type; price not shown on the page (https://azure.microsoft.com/en-us/pricing/details/trusted-signing/, 7 Oct 2026; from memory about USD 10 a month, approximate). GPU miners trip antivirus even when signed: T-Rex's README says some engines detect signatures "similar to those that real viruses protected by the same packer have" (https://github.com/trexminer/T-Rex, 7 Oct 2026).
|
||||
|
||||
| Step | Today | Proposal | Hours | Gate |
|
||||
|---|---|---|---|---|
|
||||
| Mac signature | ad hoc, quarantine strip | Developer ID plus notarisation and stapling in `build-dmg.sh`; the quarantine strip removed | 3 plus the project lead's USD 99 | a fresh macOS 26 machine opens the DMG's app with no Privacy and Security visit |
|
||||
| Windows signature | none | Authenticode through Trusted Signing or an OV certificate, signed on the box as a job step; the sha256 and the signer's name on the download page (Q50, Q82) | 3 plus the project lead's purchase | a fresh Windows 11 machine runs the installer with no SmartScreen interstitial by the 1,000-download mark (reputation is use; before that the page shows the exact interstitial text and the two clicks) |
|
||||
| Firewall prompt | UAC 20 to 50 s in, unexplained | the Cards screen names it and the step runs after `setup_done` (Q14) | 1 | the prompt never appears before a screen that says it will |
|
||||
| Antivirus | nothing | a "What your antivirus may say" line on `/miner#get` with the engine names and the exact action, and the submission to Microsoft's SmartScreen review on every release (the Learn page's submission route) | 1 | the line is on the page; every release's binaries are submitted the day they ship |
|
||||
| Time to the first share | not measured end to end on a fresh machine | measured on a fresh Windows 11 VM and a fresh Mac for every release by the Devnet 2 gate: download to first accepted share or block, with the clock, the sync and the dataset build as three timed steps | 2 | "first share within 10 minutes of download in 9 of 10 fresh Windows installs", published on `/evidence` |
|
||||
|
||||
### 3.2 First run: what the first hour should feel like
|
||||
|
||||
Today's three screens (Welcome, Cards, Address, the key sheet) are rated 7 to 8 of 10 by the UI 3 audit; the audit's own finding is that the one question a home miner has, "is this worth running", is not answered until the third screen, and the Cards row does not say what the card is expected to make although `site/miner-priors.json` knows the model (`miner-ui-3-audit.md` 2.1, 2.2).
|
||||
|
||||
| Screen | Add | Hours | Gate |
|
||||
|---|---|---|---|
|
||||
| Cards | under each known model: "about 25 MH/s at 91 W; on today's network that is about N blocks a day" from the prior and the live network rate | 1 | the row shows a number for every model in the priors file |
|
||||
| Cards | the tier sentence in plain words: "8 GB: mines; proves small shards", "24 GB: mines and proves" (from `prover-tiers-real-cards.md`) | 0.5 | every memory size has a sentence |
|
||||
| Address | "Start mining" starts mining; the key sheet comes before the button (the audit: "the button lies once") | 1 | view test |
|
||||
| Mine, first ten minutes | a "waiting for your first block" line with the live expectation: "a card like yours finds a block about every 1.6 hours on today's network; chance so far: 31 percent" (a Poisson line from the card's own rate and `/api/stats` network rate; the pool mode shows "first share in N s" instead) | 2 | the line is on screen from the first job until the first block, then is replaced by the block card |
|
||||
|
||||
The Poisson line is the piece no miner app has. 2miners shows luck for the pool; nothing shows a solo miner's own running chance. It turns the dead first hour into a count-up, and it is honest: it says 46 percent for an 8 GB card at 100 GH/s, which is why the pool switch sits one line below it.
|
||||
|
||||
### 3.3 The first block: the card, the sound, the link
|
||||
|
||||
Today: an event line in the feed and the blocks strip. Nothing is shareable, nothing is kept.
|
||||
|
||||
Proposal, the block card (Ember, Mine page, in place, no dialog):
|
||||
|
||||
```
|
||||
Block 1,284,117 is yours. 12:27:53 UK
|
||||
RTX 4070 · 25.0 MH/s · 0.025% of the network
|
||||
80.00 IGN to 0xdd44…86E8 (72 producer points, 8 for signing)
|
||||
Hash 9f3a…c21e [Open in the explorer] [Save the card] [Copy the link]
|
||||
```
|
||||
|
||||
| Element | Rule | Hours | Gate |
|
||||
|---|---|---|---|
|
||||
| The card | shown on the first block of every key, then on every 100th, 1,000th and 10,000th, and on the first block of every new card; stays until dismissed; the rest are feed lines | 2 | view test on the four thresholds |
|
||||
| Sound | off by default; one short tone on a block when on; honours reduced motion and the OS mute (`app.css` already honours reduced motion everywhere) | 0.5 | the tone never plays when the switch is off |
|
||||
| Save the card | a 1200x630 PNG drawn locally from the same data (the rig-photo post without the rig; the dark scheme, the brand mark, the card name, the hash, the explorer link as a QR) | 2 | the PNG opens; no network call is made to make it |
|
||||
| The link | `/block/<hash>` (exists: parents, children, mergeset, shards, checkpoint fractions, `block.html:285-325`) | 0 | |
|
||||
| What is not on it | no fiat, no price, no "worth": there is no market and the copy law forbids the aphorism | | |
|
||||
|
||||
The pool user gets the same card for the first share ("Share accepted by pool-0 · 0.0012 of a block") and the first credited block ("pool-0 found block N; your share 0.41 IGN"), from the `share_result` and the PPLNS credit lines the pool already sends.
|
||||
|
||||
### 3.4 The app: Earnings
|
||||
|
||||
Today (`miner-ui-3.md` section 5, polish Q18): the first number on the tab is "£0.00 earned: nothing is bought or sold on devnet"; mined IGN is never shown, only proving `paid_wei`; the electricity line is real and shown as a cost.
|
||||
|
||||
| Line | Today | Should read | Source of the number | Hours |
|
||||
|---|---|---|---|---|
|
||||
| 1 | £0.00 earned | **6,912 IGN a day** at your last hour's rate (86 blocks) | the node's own block count for the key times the subsidy at the block's DAA; the projection from the 1-h rate and `/api/stats` network rate | 2 |
|
||||
| 2 | 0.0000 IGN from 0 proofs | 2,592 blocks in 30 days, 207,360 IGN; 14 shards, 16.1 IGN | the node and the execution layer (the balance is in the wallet; the app reads the coinbase credits per key) | 1 |
|
||||
| 3 | none | about £N a day at a price you type (no exchange lists IGN; a typed price, remembered, never fetched) | the user's field; the engine's `price_gbp_per_ign` when a market exists, as the UI 3 plan already owes | 1 |
|
||||
| 4 | none | your card is 0.100 percent of the network; weight rank 41 of 1,204 keys; 30 of 30 days | `/api/live` miners and the finality weights RPC (`getFinalityWeights`, spec 3.10) | 2 |
|
||||
| 5 | £1.95 a day electricity | unchanged, and the line under it: "N IGN per kWh" | the existing watt reading | 0.5 |
|
||||
| 6 | dev fee line | unchanged (1 in 100, the switch) | | 0 |
|
||||
|
||||
A rule for every number on the tab: it comes from the node this machine runs, or from the public API with the field named on hover (polish 4.3, one formatter table). The typed price is the only number the user supplies and the tab says so.
|
||||
|
||||
### 3.5 The pool
|
||||
|
||||
Decision for the launch pool, with reasons:
|
||||
|
||||
| Question | Decision | Why |
|
||||
|---|---|---|
|
||||
| Who runs it | the project runs pool-0 at the testnet go and at mainnet genesis, on the written systemd unit (`pool/README.md`), with the member's vote key in every header as built | nobody else can on day 1; the member-key design means the project's pool never holds vote weight, which is the thing a project pool is normally criticised for |
|
||||
| Fee | 1 percent, the same as the solo dev fee, published in `welcome`, on the page and on `/miner` | a 0 percent project pool makes every outside pool unviable, and the first outside pool is a launch gate (section 4.1); 1 percent is what miners accept everywhere (2miners 1.0 percent, https://kas.2miners.com/, 7 Oct 2026; Ethermine 1 percent, approximate); fee-neutral between solo (dev fee 1 in 100) and pool, so the choice is about variance only |
|
||||
| Where the fee goes | the same software address as the dev fee; it is a software fee, documented as such (`miner-dev-fee.md`), never a protocol fee | no dev fund, no treasury; the protocol carries no fee to anyone |
|
||||
| Mode | PPLNS, window 2 blocks of weight, payout rounds every 60 s, minimum 1 IGN (as built) | the first payout inside two minutes of the first share for an 8 GB card at 100 GH/s (section 1.4) |
|
||||
| Default in the app | solo under 500 GH/s network, the app suggests the pool above it, per card: "a card like yours finds a block about every 16 hours on today's network; pool-0 pays every minute" | the Poisson line of 3.2 decides; the user chooses |
|
||||
| What the pool page shows | the connect card, the lookup, the blocks and the payments table (Q71 owed), luck in MiningPoolStats' convention (Q69, Q72), "payouts follow blue confirmation, not finality" (Q73), and the finality state | the comparator rows in polish 3.6 |
|
||||
| TLS | before the public testnet (Q67, 8 hours, consensus-engineer) | spec 9.3 |
|
||||
|
||||
The variance arithmetic that makes the pool required, computed: a single 8 GB card at 17 MH/s finds a solo block every 1.6 hours at 100 GH/s, every 16 hours at 1 TH/s (23 percent of days with no block), every 6.8 days at 10 TH/s (36 percent of weeks with no block), every 68 days at 100 TH/s. The pool is optional at launch size and required from about 1 TH/s. The pool's own payout latency: credit on blue confirmation (seconds), a round every 60 s, so under two minutes end to end once the member's balance passes 1 IGN.
|
||||
|
||||
### 3.6 Community: the Discord, the live page, the leaderboard, the miners page, the explorer
|
||||
|
||||
What exists: the Discord kit (roles Founder, Core, Prover, Miner, Bot; channels INFO, CHAIN, LEDGER, TALK; the bot posts a pulse four times a day, a daily digest, weekly numbers, releases, incidents; nothing live yet, Q7); `/live` with a Miners lane list of vote keys with a block in the last 10 minutes; `/miners` the bench table (six rows, Q51); the explorer with Miner as the payout address per block; `/address` (noindex) with balance and blocks mined.
|
||||
|
||||
| Surface | Add | Rule that keeps it honest | Hours | Gate |
|
||||
|---|---|---|---|---|
|
||||
| Discord #first-blocks | the bot posts every first block of a new key (short key id, the block link, the card model if the miner opted in through the app's "show my card" switch) | from the public API only; the forbidden-string guard; no name, no machine id | 2 | ten first blocks posted on Devnet 2 with the right links |
|
||||
| Discord roles | Voter (the key is above dust, verified by a message signed with the vote key against `getFinalityWeights`), Window (30 of 30 days), Prover (as built) | chain-side facts, anyone can check the same RPC | 3 | a role granted and revoked by the chain's numbers in a test |
|
||||
| `/live` Miners | a leaderboard by 30-day weight: rank, short key id, blocks in window, days mined, signing presence, last block; a toggle to the 10-minute view that exists | weight, never hashrate; a renter who arrived today is at the bottom whatever they rent | 3 | the page shows the top 100 by weight with days-mined; a key that stops mining falls one place a day |
|
||||
| `/address/<key or address>` | the public profile: blocks in 30 days, weight rank, vote state, checkpoints signed, shards proven, first block date, the block cards | noindex stays until the owner opts in from the app ("make my page public") | 3 | the page renders for every key in the window |
|
||||
| `/miners` | the hardware census (section 4.4): one row per card model from the hourly program's per-card timings, MH/s, W, MH/W, count of keys on that model, median first-block time | measured, scrubbed, no machine names (the bench-table rule) | 4 | eleven rows today, every model with three or more keys after the testnet go |
|
||||
| Explorer block page | "found by key id N, rank 41, day 23 of 30" under Miner | from the same weights | 1 | the line on every block |
|
||||
|
||||
### 3.7 Why I stay: the ladder written into consensus
|
||||
|
||||
The thing no other chain has: vote weight is 30 days of blocks per key (rule v2). A level system with no points server, no badge contract and no company database: the chain computes it, the node reports it (`getFinalityWeights`), the wallet verifies the certificates it signs.
|
||||
|
||||
| Rung | Name on screen | Rule | Time for a 100 MH/s card at 100 GH/s | Time for an 8 GB card at 100 GH/s | Where shown |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 | First block | one blue block with your key | 16.7 min expected | 1.6 h | the block card; #first-blocks |
|
||||
| 1 | Your key has a vote | 100 blocks in the window (the dust line) | 1.2 days | 6.8 days | Mine hero, address page, Discord Voter |
|
||||
| 2 | Your signature is in checkpoint K | the first certified checkpoint carrying the key's vote | the next 30-s checkpoint after rung 1 | the same | the finality line in Mine, the block page of the lock |
|
||||
| 3 | Full window | 30 of 30 days with blocks | day 30 | day 30 | the days ring in the hero; Discord Window |
|
||||
| 4 | Rank | position by weight among all keys | continuous | continuous | `/live` leaderboard, Earnings line 4 |
|
||||
| 5 | Signing streak | days since the key last missed the presence window (the 8 points never lost) | continuous | continuous | Earnings: "signing 8 of 80 points, 41 days unbroken" |
|
||||
| P | Your card proved shard N of block M | a paid shard record | hours on a 24 GB card; not yet on 8 GB | not yet | the Prove tab card, Discord Prover |
|
||||
|
||||
The exact lines, in copy law: "Your key has a vote." "Your signature is in checkpoint 184,220." "Your card proved shard 3 of block 1,284,117." "30 of 30 days. Full weight." "Rank 41 of 1,204 keys." Each line names the chain fact behind it on hover (the RPC field), the same rule as every number on the site.
|
||||
|
||||
What the rungs pay, so the ladder is not decoration: rung 1 is the vote itself; rung 2 starts the 8-point signing line (the public sentence: miss two hours of signing and the next blocks pay 72 until you sign again); rung 3 is maximum weight; rung P is the proving income (20 points of every block, split by shard). Nothing is paid from a fund; the ladder only names what the rules already pay.
|
||||
|
||||
### 3.8 Per tier: the first hour and the first month
|
||||
|
||||
Cards at the measured untuned rates of `docs/analysis/prover-tiers-real-cards.md` (rented, 6 October 2026) and the bench table; expectations at 1 block a second; the first month at full rate (100 IGN a block) and at the 90-day ramp approved for the testnet genesis on 7 October (10 percent on day 1, so divide the IGN column by 10 on launch day; CLAUDE.md's 30-day ramp is the earlier text).
|
||||
|
||||
Network 100 GH/s:
|
||||
|
||||
| Tier | Gap to a solo block | Block in the first hour | Blocks a day | IGN a day (80 points) | Days to a vote | First month: blocks, and what the key is |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 8 GB, RTX 4060 (17.07 MH/s) | 1.6 h | 46 percent | 14.7 | 1,180 | 6.8 | 442 blocks; a voter from day 7; full window day 30; proves core-only shards (`prover-tiers` row 4060) |
|
||||
| 8 GB, RX 9070 XT (17.9) | 1.6 h | 48 percent | 15.5 | 1,237 | 6.5 | 464; a voter from day 7; no proving (CUDA only today) |
|
||||
| 12 GB, RTX 4070 (24.99) | 1.1 h | 59 percent | 21.6 | 1,727 | 4.6 | 648; a voter from day 5; mines and proves on the patched prover when shipped |
|
||||
| 12 GB, RTX 5070 (41.89) | 40 min | 78 percent | 36.2 | 2,895 | 2.8 | 1,086; a voter from day 3 |
|
||||
| 16 GB, RTX 4060 Ti (17.58) | 1.6 h | 47 percent | 15.2 | 1,215 | 6.6 | 456; a voter from day 7; mines and proves |
|
||||
| 24 GB, RTX 4090 (52.25) | 32 min | 85 percent | 45.1 | 3,612 | 2.2 | 1,354; a voter from day 3; mines and proves today |
|
||||
| 32 GB, RTX 5090 (98.48 rented; 122 on PC 1) | 17 min | 97 percent | 85.1 | 6,807 | 1.2 | 2,553; a voter from day 2; proves beside the miner |
|
||||
| Apple M5 Max, Metal (26.7) | 1.0 h | 62 percent | 23.1 | 1,846 | 4.3 | 692; a voter from day 5; proves on the CPU, slowly |
|
||||
| Rig, 6 x RTX 4090 (313.5) | 5.3 min | 100 percent | 271 | 21,669 | 0.4 | 8,126 (one key per machine, the 6 October default) |
|
||||
| Pool member, any of the above | first share under a minute; first credit under two minutes | 100 percent | the same blocks on average, paid every minute | the same minus 1 percent | the same days (weight follows the member's own blocks) | the same |
|
||||
|
||||
Network 1 TH/s (the same cards):
|
||||
|
||||
| Tier | Gap to a solo block | Block in the first hour | Blocks a day | IGN a day | Days to a vote | 30-day blocks |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 8 GB, 17.07 MH/s | 16.3 h | 6 percent | 1.5 | 118 | 68 (never inside the window) | 44 |
|
||||
| 12 GB, 24.99 | 11.1 h | 9 percent | 2.2 | 173 | 46 (never) | 65 |
|
||||
| 12 GB, 41.89 | 6.6 h | 14 percent | 3.6 | 290 | 28 | 109 |
|
||||
| 24 GB, 52.25 | 5.3 h | 17 percent | 4.5 | 361 | 22 | 135 |
|
||||
| 32 GB, 98.48 | 2.8 h | 30 percent | 8.5 | 681 | 12 | 255 |
|
||||
| Apple M5 Max | 10.4 h | 9 percent | 2.3 | 185 | 43 (never) | 69 |
|
||||
| Rig, 6 x 4090 | 53 min | 68 percent | 27.1 | 2,167 | 3.7 | 813 |
|
||||
|
||||
Per OS and vendor, what differs in the first hour: Windows, two prompts (section 3.1) and NVIDIA through the driver, AMD and Intel through OpenCL; macOS, one prompt, Apple silicon through Metal, no watt reading so no pounds line; Linux and HiveOS, no prompt, the Flight Sheet fields, Hive itself untested on a real rig (`/miner`). AMD cannot prove today (CUDA only). The 12 GB tier cannot mine and prove at once on the shipped build (peak 15.6 GB measured, bench log 5 October); the patched prover's 10.1 GB peak beside the miner on the 4070 is measured and not shipped.
|
||||
|
||||
### 3.9 The first withdrawal, and the first "I own something"
|
||||
|
||||
No exchange at the testnet, none arranged or sought at mainnet (journey phase 6). So the first ownership moment is not a withdrawal. It is:
|
||||
|
||||
| Moment | Today | Proposal | Hours |
|
||||
|---|---|---|---|
|
||||
| The balance | in the wallet ("the balance is in the wallet", Earnings); the address page shows balance and blocks | the Earnings tab shows the balance read from this machine's node, with "verified by this node" on hover | 1 |
|
||||
| The proof | the wallet verifies finality certificates with the node's own code (`finality.rs:101-121`) | the first block card carries "final under checkpoint K, certificate verified by this machine" once the lock lands, and the address page shows the latest locked checkpoint (Q37) | 1 |
|
||||
| The QR | the wallet draws the `ethereum:` URI locally (`server.rs:140`) | the saved block card carries the explorer link as a QR; the address page's QR under "make my page public" | 0.5 |
|
||||
| The send | EIP-1559 value transfers in the wallet | a first-transfer card in the wallet: "sent 10 IGN to 0x…, in block N, final under checkpoint K" with the state words pending, included, executed, proven, finalised (UI 3) | 0 beyond Q9 |
|
||||
|
||||
"I own something" on Igneum is "a block with my key is final under a certificate my own machine verified". No other chain's miner app says that sentence.
|
||||
|
||||
## 4. The exact structure for Igneum
|
||||
|
||||
### 4.1 The launch sequence as gates
|
||||
|
||||
The go checklist (`docs/plans/testnet-go.md`) has eleven steps, from the seeds at height 0 to the first miner's block 1. These are the community gates that follow it; none has a date.
|
||||
|
||||
| Gate | Definition | How it is measured | What opens when it passes |
|
||||
|---|---|---|---|
|
||||
| G-C1 First 100 keys | 100 distinct vote keys with a block in the last 24 h | `/api/live` miners over a day | the #first-blocks channel goes live; the leaderboard shows ranks |
|
||||
| G-C2 First block by a card the project does not own | a block whose key is not in the project's fleet list | the observer's key list against the fleet file (the Discord guard already reads it) | the "first outside block" post, with the owner's permission |
|
||||
| G-C3 First outside-reproduced benchmark | a card row on `/miners` measured by someone outside the project, with driver, OS and version (Discord rule 6) | the bench-table ingest with `by: reported by a miner` | the census starts (4.4) |
|
||||
| G-C4 First pool not run by the project | a pool at an address the project does not control with 10 percent of blocks for 7 days, the member key in its headers | the explorer's payout-address share | pool-0 stays at 1 percent; the app's pool list gains a second entry |
|
||||
| G-C5 First 1,000 independent keys | X5 as decided on 6 October: 1,000 keys above dust over 30 days, independent in autonomous system, machine fingerprint and pool attestation, top-10 share of window weight under 50 percent; a silent key counts only if its AS is its own | the observer's AS and fingerprint columns (owed, one app-owner item) | journey phase 5 closes; mainnet genesis is the next gate |
|
||||
| G-C6 First outside proving customer | one rollup's job proven and paid on its own chain | the job market's payout contract | phase 4's second half |
|
||||
|
||||
### 4.2 The first 1,000 miners: where they come from
|
||||
|
||||
The no-airdrop constraint shapes every row: nothing is offered for joining; the only pay is mined emission; the message is the number and the file. Costs are agent hours for the content and the answering; the expected counts are this lane's estimates and labelled so.
|
||||
|
||||
| Channel | Expected keys (approximate) | Hours | The message | Rule |
|
||||
|---|---|---|---|---|
|
||||
| r/gpumining (the Reddit kit's day-7 post: eleven cards, MH/s, W, MH/W) | 150 to 300 | 6 (post, 48 h of answers) | the card table; "devnet coins have no value; nothing is for sale"; the ledger | rule 2.10 (no referral or promo links); the founder reads the Expanded Rules first |
|
||||
| r/EtherMining (the Merge story, under rule 7's disclosure) | 100 to 200 | 4 | four years after the Merge, what runs and the command to check it | one linked post per seven days; nobody sent to Discord |
|
||||
| HiveOS forum and the custom-miner listing | 100 to 250 (rigs, so more hashrate than keys) | 4 plus the first real Hive run | the HiveOS card verbatim, "Hive itself is untested so far; report what breaks" | the first reported rig run gets a bench row and the Miner flair |
|
||||
| Bitcointalk ANN | 50 to 150 | 3 plus daily answers for a week | the ANN format: no premine, no sale, the dev fee, the card table | no bounty, no signature campaign |
|
||||
| Miner Discords (lolMiner, BzMiner, Hive, the Kaspa and Ergo mining rooms) | 100 to 200 | 3 | two sentences, the card table, the ledger, "nothing is for sale" | one message per server in its projects channel |
|
||||
| The miner-software authors (lolMiner, BzMiner, Team Red, Rigel) | 0 to 300 keys on the day one of them ships Igneum | 8 (the worker protocol document, the vectors, the dev-fee template mechanism explained so their fee works in solo mode) | "a fee template is 1 in 100 by a counter; your fee rides the same mechanism" | their fee is theirs; the protocol carries none |
|
||||
| r/CryptoTechnology, r/zkproofs, r/Monero (the design rooms) | 30 to 80 | 4 | the prover, the verify command, the vs RandomX table | comments only where the rules say so |
|
||||
| The hardware census itself (4.4), once G-C3 passes | 100 to 300 over the first months (people come to get their card measured on a public board) | 0 beyond the board | "your card's row, measured by the chain, not by us" | scrubbed, no machine names |
|
||||
| Total | 630 to 1,780 keys, median about 1,000 | 32 | | |
|
||||
|
||||
Every row is the Reddit kit's existing text or an extension of it; the only new content is the census board and the worker-protocol document for the miner authors.
|
||||
|
||||
### 4.3 The pool at launch
|
||||
|
||||
Decided in 3.5: project-run pool-0 at 1 percent, member keys in headers, PPLNS, 60-s rounds, the dev fee off in pool mode because the pool fee is the same 1 percent to the same software address. The outside pool is a gate (G-C4), not a hope: the pool crate is public with its README, and the app's pool field takes any host.
|
||||
|
||||
### 4.4 The leaderboard and the census
|
||||
|
||||
| Board | Ranks | Never ranks | Source | Why |
|
||||
|---|---|---|---|---|
|
||||
| Keys (`/live`, the address page, Earnings line 4) | 30-day weight (blue blocks per key in the window), with days mined and signing presence | hashrate | `getFinalityWeights` | a renter who arrives today has no weight; a laptop that mined for 30 days outranks a rented rig's first week; the board is the chain's own security table, so it cannot lie without the chain lying |
|
||||
| Cards (`/miners`, the census) | efficiency per card model: MH/s, W, MH/W, and the per-program timing spread from the hourly swap (the frontier lane's "8,760-question hardware census", `frontier.md` 4.3) | people | the hourly program's per-card timings, the bench-table ingest, scrubbed | the public benchmark the litepaper promises, made continuous; a chip that does better than the GPU curve shows up on this board first |
|
||||
|
||||
The census is new: RandomX programs are per hash and no pool sees their timing; Igneum's hourly program with per-card racing produces the census as a by-product (`frontier.md` 4.3). It is the one leaderboard a miner wants that no chain has, and it doubles as the chip alarm.
|
||||
|
||||
### 4.5 The story each miner tells
|
||||
|
||||
Written in copy law (short sentences, no antithesis, no aphorism).
|
||||
|
||||
| When | The three sentences |
|
||||
|---|---|
|
||||
| First week | "I installed it in five minutes and the first block was mine inside the hour. Every block pays my address straight away. My 4070 is on a public board with its watts." |
|
||||
| First month | "My key has a vote now. It signs finality every 30 seconds and I can see the checkpoint it is in. After 30 days my weight is full, and the rented farms that showed up last week sit under me." |
|
||||
| First year | "The program has changed 8,760 times and my card still mines it. My card has proved 400 shards for a rollup I have never heard of. Nothing was sold and nothing was given away: every coin I hold was mined." |
|
||||
|
||||
## 5. The surfaces to change
|
||||
|
||||
| Surface | Today (polish.md) | Should show | Hours | Gate |
|
||||
|---|---|---|---|---|
|
||||
| Ember, Cards | card name, switch, "worker Metal, 40 GPU cores" (audit 2.2) | expected MH/s and W from the priors, "about N blocks a day on today's network", the proving tier sentence | 1.5 | every model in the priors shows a number |
|
||||
| Ember, Mine (before the first block) | the blocks strip, 10 minutes (`app.js:1052-1127`) | the Poisson count-up line; the pool suggestion when the gap passes 8 h | 2 | the line shows from the first job to the first block |
|
||||
| Ember, Mine (the block card) | an event line | the block card of 3.3, saved PNG, the explorer link | 4.5 | the four thresholds; the PNG with no network call |
|
||||
| Ember, Earnings | "£0.00 earned", mined IGN never shown (Q18) | IGN a day, blocks and IGN in 30 days, shards and IGN, a typed price, share of network, weight rank, days, signing streak | 6.5 | every line names its RPC field on hover; a view test per line |
|
||||
| Ember, Prove | state line "0 assigned · 0 proven · 0 paid" | the shard card "your card proved shard N of block M, 1.15 IGN" | 1 | a card on the first paid record on Devnet 2 |
|
||||
| Ember, Settings | pool field designed, not built (`pool.md` section 7) | the pool field, the terms box from `welcome`, the dev-fee line reading "off in pool mode: the pool's 1 percent is the same fee" | 3 | a member connects from the app and shows the terms |
|
||||
| Ember, first run | two prompts, one unexplained (Q3, Q14) | signed and notarised; the firewall step named on the Cards screen | 7 plus the project lead's certificates | 3.1 gates |
|
||||
| Wallet | one account, no finality state on the address page (Q33, Q37) | the first-transfer card with the five state words; the address page with the latest lock | 3 (plus Q9's 6) | a devnet transfer walks the five words on screen |
|
||||
| Site `/miner` | "Install. Start. The card mines and proves." plus feature lines | the first-hour timeline (download, the one prompt, first share, first block, first payout) with the measured times from the Devnet 2 gate | 2 | the times on the page equal the gate's log |
|
||||
| Site `/live` | miners with a block in 10 minutes | the weight leaderboard with days mined and signing presence; the 10-minute view as a toggle | 3 | top 100 by weight; a stopped key falls one place a day |
|
||||
| Site `/miners` | six rows, two models, "not measured" MH/W (Q51) | the census: one row per model, MH/s, W, MH/W, keys on that model, median first-block time | 4 | every model with three or more keys |
|
||||
| Site `/address` | balance, blocks, noindex | the public profile of 3.6 behind the app's opt-in | 3 | renders for every key in the window |
|
||||
| Explorer block page | Miner = payout address | "found by key id N, rank 41, day 23 of 30" | 1 | on every block |
|
||||
| Pool page | stat strip, connect, lookup, blocks, no payments table (Q71) | payments, luck in the standard convention, "payouts follow blue confirmation, not finality", finality state | 3 | the comparator rows in polish 3.6 |
|
||||
| Discord | nothing live (Q7); roles Miner and Prover by hand | the bot live; #first-blocks; Voter and Window roles from chain facts | 8 | ten first blocks on Devnet 2 posted; a role granted by a signed message |
|
||||
| Total | | | 52.5 plus the certificates | |
|
||||
|
||||
## 6. What not to build
|
||||
|
||||
| Idea | The line that kills it |
|
||||
|---|---|
|
||||
| Gamification paid from a fund (a weekly prize, a "block of the day" bonus) | there is no fund; every coin is mined under the 80/20 rule and the signing bonus, and a prize paid by the project is a premine by another name |
|
||||
| Referral in coins | no referral paid in coins from a fund: there is no fund; and r/gpumining rule 2.10 bans referral codes outright |
|
||||
| An airdrop for installing, posting or reproducing a benchmark | an airdrop in exchange for anything is a sale by another name; the rule is "nothing is for sale" on every surface |
|
||||
| NFT badges for the rungs | the rung is a chain fact already (weight, days, signatures); a token for it adds a contract, a market and a price to a thing that must stay a number |
|
||||
| An off-chain points system | a number a company server has to be trusted for; the ladder's rule is that every number is a chain fact anyone can read from an RPC |
|
||||
| A rank-by-hashrate board | a renter tops it on the first day; the only board is weight, which takes 30 days |
|
||||
| A "top earners" board in fiat | there is no price; a typed price is the user's and stays on their machine |
|
||||
| A Discord level bot (MEE6-style XP for messages) | a number a server owner sets; it rewards talking, not mining |
|
||||
| A bounty for the first ASIC, or a device bounty | ruled out (M1, M22); a device bounty is a sale of the chip |
|
||||
| Streaks that penalise a miss (lost rungs, demotion) | the signing bonus already prices a miss (72 until you sign again) and nothing is burned; a second penalty on top is the burn the owner refused |
|
||||
| Any surface whose number needs a company server to be trusted | the observer, the API and the Discord bot are conveniences; every number they show must be reproducible from a node, or it is not shown |
|
||||
|
||||
## 7. Verdict table
|
||||
|
||||
| Proposal | Evidence (chain, year, number) | Newness | Hours | Gate | Recommend |
|
||||
|---|---|---|---|---|---|
|
||||
| Signed and notarised installers | SmartScreen warns on any file without reputation (Microsoft Learn, 2026); polish Q3 | everyone has it; we lack it | 7 plus certificates | no interstitial on a fresh Mac; the Windows one gone by 1,000 downloads | do now |
|
||||
| The Poisson count-up before the first block | 2miners shows pool luck (kas.2miners.com, 2026); no app shows a solo miner's own running chance | nobody has it | 2 | the line shows until the first block | do now |
|
||||
| The block card with a saved PNG | EtherMining's rig photo 2,528 points in 2021 (section 1.1); the Helium miner photo 860 in 2021 | nobody has it in a miner app (approximate) | 4.5 | PNG with no network call | do now |
|
||||
| Earnings in IGN first, a typed price second | NiceHash shows fiat first (polish 3.1, approximate); Q18 | someone has fiat; nobody has "typed price, never fetched" | 6.5 | every line names its field | do now |
|
||||
| The weight ladder: vote, signature, full window, rank, streak | rule v2 (Igneum, 2026): weight is 30 days of blocks per key | nobody has it | 6 (across Ember, address page, Discord) | a Devnet 2 key walks every rung on screen | do now |
|
||||
| The weight leaderboard on `/live` | Kaspa pools rank workers by hashrate (approximate); Helium ranked hotspots by HNT (section 2) | nobody ranks by consensus weight | 3 | top 100 by weight | do now |
|
||||
| The hardware census on `/miners` | frontier 4.3; the litepaper's promised benchmark | nobody has it | 4 | every model with three keys | do now |
|
||||
| Pool-0 at 1 percent, member keys | 2miners 1.0 percent fee (2026); spec 09 member votes | the member-key pool is new; the fee is standard | 0 beyond Q67 to Q73 (about 20) | G-C4 | do now |
|
||||
| #first-blocks and the Voter and Window roles | Discord kit roles (2026); chain-side facts | nobody grants a role from consensus weight (approximate) | 5 | roles from a signed message | do now |
|
||||
| The public address profile behind an opt-in | Helium's explorer hotspot page (2021), section 2 | someone has it (Helium); ours adds the vote and the signatures | 3 | renders for every key | prototype |
|
||||
| The first-hour timeline on `/miner` with measured times | the devnet laptop's 7 minutes and the 5090's first minute (bench log, 4 Oct 2026) | ethereum.org and kaspa.org have no measured install time (approximate) | 2 | the page equals the gate's log | prototype |
|
||||
| The worker-protocol document for miner authors | lolMiner 0.75 percent Kaspa fee, T-Rex 1 percent (their READMEs, 2026) | standard | 8 | one outside miner ships Igneum | prototype |
|
||||
| A dust line that scales with the network | section 1.4: an 8 GB card never votes past about 500 GH/s | a finality-lane question | 0 here | the finality lane's answer | watch |
|
||||
| Sound on a block | every pool page has a block sound option (approximate) | standard | 0.5 | off by default | watch |
|
||||
| Anything in section 6 | | | | | never |
|
||||
|
||||
## 8. Three headline findings for the coordinator
|
||||
|
||||
1. At the litepaper's 100 GH/s network a solo 8 GB card finds its first block in 1.6 hours expected and 46 percent of them see no block in the first hour, so the first-hour dopamine for the most common tier is the Poisson count-up line and the pool's first share under a minute, not the block; at 1 TH/s that card finds a block every 16 hours and never reaches the 100-block vote line (44 blocks in 30 days), which the finality lane must weigh against the 1,000-independent-keys gate.
|
||||
|
||||
2. Igneum's 30-day vote window is a level system no other chain has, and it is free: the rungs (first block, 100 blocks for a vote, a signature in a checkpoint, 30 of 30 days, rank by weight, a signing streak) are chain facts from `getFinalityWeights`, cost about 6 agent hours to show across Ember, the address page and Discord, and cannot be topped by a renter inside 30 days.
|
||||
|
||||
3. In a 105-post sample of miner joy on Reddit (69 with a room median), the posts that rise highest are the miner's own: "my card paid for itself" at 777 times the room's typical score, the rig photo at 417, the hashrate milestone at 409, against 66 for the network's own milestone and 56 for the first pool payout; so every shareable surface Igneum builds carries the miner's card, rank and days, not the project's number, and the saved block card (4.5 hours) is the first of them.
|
||||
|
||||
## Sources
|
||||
|
||||
Repository files (the mission worktree, read 7 October 2026): `CLAUDE.md`; `docs/analysis/horizon/polish.md` (sections 1, 3.1, 3.2, 3.4, 3.6, 3.7, 3.10, 4.5); `docs/analysis/vote-or-burn.md` section 5; `docs/analysis/tail-emission.md`; `docs/analysis/horizon-2026-10.md` section 1; `docs/plans/ledger-decisions.md` (item 3, the standing decisions, the 6 and 7 October decisions); `docs/plans/pool.md`; `pool/src/config.rs`; `docs/spec/09-pool-protocol.md`; `docs/plans/miner-ui-3.md` and `miner-ui-3-audit.md`; `docs/plans/testnet-go.md`; `docs/design/miner-dev-fee.md`; `docs/community/discord-hooks.md`; `docs/analysis/prover-tiers-real-cards.md`; `docs/analysis/horizon/frontier.md` 4.3; `docs/bench-log.md` (4 and 5 October 2026 entries); `site/journey.json`, `site/live.html`, `site/explorer.html`, `site/miner.html`, `site/miner-bench.json`; the litepaper text; the Reddit kit on branch `reddit-kit` (`docs/community/reddit/outreach.md`, `outreach-week-1.md`, `discord.md`).
|
||||
|
||||
Fetched by this lane (all 7 October 2026):
|
||||
- https://2miners.com/faq (payouts every 2 hours; thresholds per coin page)
|
||||
- https://kas.2miners.com/ (1.0 percent fee, 50 KAS minimum, 771 miners, luck 441 percent, last block 2 minutes ago)
|
||||
- https://solo-kas.2miners.com/ (SOLO fee 1.5 percent, 99 miners)
|
||||
- https://p2pool.io/ (min payout 0.00027 XMR; "several days to a week to find a share")
|
||||
- https://github.com/Lolliedieb/lolMiner-releases (Kaspa fee 0.75 percent, Autolykos 1.5 percent)
|
||||
- https://github.com/trexminer/T-Rex (1 percent, 2 percent on Octopus and Autolykos2; the antivirus note)
|
||||
- https://learn.microsoft.com/en-us/windows/security/operating-system-security/virus-and-threat-protection/microsoft-defender-smartscreen/ (page dated 23 April 2026)
|
||||
- https://www.ssl.com/code-signing/ (reputation through use for OV and EV)
|
||||
- https://support.apple.com/en-us/102445 (Open Anyway)
|
||||
- https://developer.apple.com/programs/ (USD 99 a year) and https://developer.apple.com/support/compare-memberships/ (notarisation included)
|
||||
- https://shop.certum.eu/open-source-code-signing-on-simplysign.html (from EUR 49, out of stock)
|
||||
- https://azure.microsoft.com/en-us/pricing/details/trusted-signing/ (Basic 5,000 signatures a month; price not shown)
|
||||
- https://en.wikipedia.org/wiki/Helium_Network and https://en.wikipedia.org/wiki/Chia_(cryptocurrency)
|
||||
- https://api.pullpush.io/reddit/search/submission/ (r/EtherMining, r/chia, r/HeliumNetwork, r/kaspa listings used as a cross-check of the sub-lane's sample)
|
||||
|
||||
Fetched by the Reddit sub-lane (scratch `mission-reinvent/reddit-sample.md`, 105 posts, every URL in its table 1 and quotes in table 3; the Arctic Shift archive for medians; the ninjastic archive for bitcointalk). The URLs cited in sections 1.1 and 2 above are from that file.
|
||||
|
||||
Fetched by the chains sub-lane (scratch `mission-reinvent/chains.md`, 96 URLs in its sources list): the Helium HIPs 10, 15, 17, 20, 25, 51, 53, 54, 70 and the denylist repository; the Chia blog posts of 11 Nov 2020, 23 Feb, 17 Mar, 24 May, 13 Jun, 15 Jun, 30 Jun, 7 Jul, 3 Aug 2021 and 19 Mar 2022 and releases 1.0.0, 1.1.0, 1.2.0; Etherscan's hashrate CSV; bitinfocharts ETH and ETC; Decrypt on ETC; api.kaspa.org; the BzMiner, lolMiner and Team Red release pages; asicminervalue; the Monero node RPC, xmrchain, the RandomX repository and getmonero.org; the Ravencoin whitepapers, releases and blockbook; the Ergo releases, docs and explorer API; the 2miners stats APIs and blog; CoinGecko for every price. Pages that refused the fetch (Mashable, The Verge, Bloomberg, Forbes, CNBC, Polygon, PCMag) are cited through Wikipedia and marked so in that file.
|
||||
|
||||
Figures labelled approximate come from memory and were not pinned to a page today.
|
||||
371
docs/analysis/mission/unfinished.md
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
# The last mission, lane 2: started and not finished, across the industry
|
||||
|
||||
7 October 2026. Lane 2 of the final research programme. What the industry started and did not finish, group by group, with one verdict per row: Igneum already has the finished version, Igneum could finish it in hours, or Igneum should leave it. Every figure from a source carries its URL and access date in section 9; every figure from memory is labelled approximate. Hours are agent hours (the project lead's rule: Claude-side work takes hours, never weeks). Nothing here is a token sale.
|
||||
|
||||
What this file does not repeat: the ASIC chip history (`docs/analysis/asic-resistance-history.md`), the useful-work verdict and the stored-state candidate (`new-pow.md` sections 3, 6, 7), the ranked frontier ideas (`frontier.md` section 0, 3.10, 3.11), and Igneum's own pool design (`docs/plans/pool.md`, `docs/spec/09-pool-protocol.md`). Those are cited, not restated.
|
||||
|
||||
## 0. Progress (UK time)
|
||||
|
||||
| Time | State |
|
||||
|---|---|
|
||||
| 08:1x (approximate) | Context read: CLAUDE.md, asic-resistance-history.md, new-pow.md 3, 6, 7, frontier.md 0, 3.10, 3.11, horizon-2026-10.md 1, pool.md, spec 09, polish.md 3.1, 3.6, 3.10 |
|
||||
| 08:2x (approximate) | Four research lanes launched (A, B, C with D, E). Lane F was refused by the concurrency cap and was researched by this lane directly |
|
||||
| 08:3x (approximate) | The session's WebSearch budget ran out (200 of 200). Every later fact came from WebFetch of a primary page or is labelled approximate |
|
||||
| 08:49 | Lane F notes saved (scratch `mission-unfinished/F-notes.md`, file time) |
|
||||
| 08:50 | Igneum-side pool and Ember comparison drafted (scratch `igneum-side-draft.md`) |
|
||||
| 08:53 to 08:59 | Lanes A, E, B, C with D landed (scratch file times) |
|
||||
| 09:09 | All notes read; assembly of this file started |
|
||||
| 09:17 | File complete: 371 lines, em-dash grep at zero, nothing staged |
|
||||
|
||||
Limits of this research: p2pool.observer answered 502 all morning (Monero P2Pool share taken from the miningpoolstats data file instead); Bob Rao's ProgPoW PDF would not extract (his 1.1x to 1.2x figure is cited to EIP-1057); Bitmain's own X9 page was unreachable (the X9 is reported as announced per resellers, unconfirmed shipping); NiceHash's fee and repayment pages are gone (those figures are approximate); Salad's take rate is not published anywhere fetched.
|
||||
|
||||
## 1. Group A: proof of useful work
|
||||
|
||||
The one question per row: can the useful work be verified cheaper than it is done, and is it sampleable (a nonce picks a random instance the miner cannot steer, with tunable hardness)? The third column the rows keep answering by themselves is whether anyone wanted the output.
|
||||
|
||||
### 1.1 The rows
|
||||
|
||||
| # | Programme | Attempted, by whom, when | Achieved (numbers) | Left undone | Why it stopped | Verified cheaper than done | Sampleable |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | Primecoin | Sunny King, 7 Jul 2013: header hash is the origin of a Cunningham or bi-twin prime chain | 13-prime 94-digit chain 10 Sep 2013; first length-15 twin chain 30 Mar 2018; height 7,038,822 today | Any use of the primes; GPU resistance (GPU miners by 2014, approximate) | Nobody needed the output; price USD 0.034, "stopped trading on all exchanges"; last release 26 Jan 2021 | Yes, but polynomial: 10 to 15 modexps to verify against millions of sieve steps (approximate) | Yes: the hash fixes the origin, chain length plus fractional remainder tunes hardness |
|
||||
| 2 | Gridcoin | Rob Halford, "Proof of Research" live 11 Oct 2014 on BOINC credit | 17 whitelisted projects, 3 greylisted; release 5.5.0.0 on 3 Apr 2026 (78 PRs, 11 contributors) | A verifiable work primitive; consensus moved to pure PoS 2014 to 2015 | Scraper nodes read credit from project servers and sign a superblock; the chain never checks a work unit | Only by trust (an oracle reads a website) | No: the miner picks the project and the unit; the project sets hardness |
|
||||
| 3 | Curecoin, FoldingCoin | 2014, Folding@home points paid daily (CURE) or monthly (FLDC on Counterparty) | 1 trillion F@h points by 28 Jul 2019, over 10,000 team members | Verification by anyone but Stanford's servers; Curecoin 2.0 roadmap undated; FoldingCoin asset archived, site gone | Tip jar on a centralised stats feed; token value fell under the electricity (approximate) | Only by trusting F@h | No: F@h assigns the work |
|
||||
| 4 | Aleo PoSW to synthesis puzzle | Proof of Necessary Work (Kattis, Bonneau, 2020/190); testnet 3 coinbase puzzle 2023; mainnet 17 Sep 2024 on AleoBFT with the puzzle outside consensus; ARC-41 synthesis puzzle | 750 M proofs per second and over 44,000 provers on testnet 3 (Messari excerpt); 2/3 of coinbase to provers; about 16 pools took close to 90 percent of puzzle rewards; ARC-46 (31 Jul 2025) demands 100,000 ALEO staked per solution, 2,500,000 by 2027 | The original promise: energy that produces proofs of real state. The puzzle proves nothing the network uses | A BFT validator set gives finality; the puzzle survives only to pay hardware and keep a prover fleet warm; proving centralised on the same path as mining | Yes (SNARK verify in milliseconds) | Yes (epoch hash plus nonce, target tunable). Useful: no |
|
||||
| 5 | Ofelimos | Fitzi, Kiayias, Panagiotakos, Russell, eprint 2021/1379, CRYPTO 2022: doubly parallel local search, every step a hashed PoW unit | A security proof in the backbone model; WalkSAT variant "competitive with vanilla WalkSAT" | Any deployment; who submits problems and pays; the search's own efficiency (FRLS follow-up 14 Nov 2025 calls DPLS "particularly wasteful") | Economics and operations, not the proof | Yes (each step is a cheap check) | Partly: the step is nonce-bound, the instance comes from a client pool, so hardness is tuned on the hash part |
|
||||
| 6 | PoW from worst-case assumptions | Ball, Rosen, Sabin, Vasudevan, eprint 2017/203 and 2018/559: Orthogonal Vectors, 3SUM, APSP with a delegation verifier | The conditions: worst-case to average-case reduction, non-amortisability, fast verification | Any implementation or benchmark; a buyer for random OV instances | The instances must be drawn from a distribution no real problem has; polynomial blow-ups | Yes (near-linear verify against quadratic solve) | Yes by construction. Useful: no in practice |
|
||||
| 7 | Filecoin PoRep and PoSt | Protocol Labs, mainnet Oct 2020: seal a 32 GiB sector (hours of CPU plus 20 to 30 min GPU SNARK, 128 to 256 GiB RAM), then daily WindowPoSt | Utilisation 29 percent (Q1 2025), 36 percent (Q3 2025), 1,110 PiB of real data on 3.0 EiB committed; raw byte power 3.01 EiB (29 Sep 2025) to 1.35 EiB (14 Sep 2026), down 55 percent; 99.5 percent of Q3 2025 fees were penalties | Paid demand; two thirds of proved bytes were zero-filled capacity; the chain now pivots to PDP hot storage (May 2025) that earns no consensus power | Sealing cost was paid by emission, not customers; when rewards fell providers left | Yes (one seal, then a sub-second daily proof) | Yes (random leaf challenges per deadline). The only row that held both for a real good, at hours per 32 GiB |
|
||||
| 8 | Chia proof of space | Chia Network, mainnet May 2021: plots of random tables, challenge per signage point | 1.5 EiB to 15 EiB in May 2021 alone, peak 36.5 EiB Jul 2021; a k32 plot needs 1.3 TiB of SSD writes; pooling Jul 2021; compression Jan 2023; under 4 EiB effective and under 3 EiB raw in Sep 2026 (about 10 percent of peak); XCH USD 1.36 against a USD 1,934.51 peak; Chia 3.0 (27 Feb 2026) resets plots to 1 GiB k28 with a 256-day phase-out | Anything stored; the space holds nothing | Price down 99.9 percent, reward per TB collapsed, no use for the plots | Yes (a few hashes) | Yes (challenge, plot filter, k). Useful: no |
|
||||
| 9 | Flux, Akash | Compute markets beside a coin: Flux (2018 as ZelCash, 7,015 nodes on 5 Oct 2026 against a 14,000 peak, no published revenue in six years, "PoUW v2" Oct 2025 = hardware benchmarking); Akash (Q1 2026 lease revenue USD 253,250, 58 providers, 84 GPUs in use of 334) | Node counts, not verified work | A chain that checks the compute; InFlux Technologies entered administration 4 Sep 2026 | Block rewards pay for idle capacity; demand is two to three orders under the subsidy | No (reputation and benchmarks) | No (customers pick the job) |
|
||||
| 10 | Dynex | 2022 to 2026, "neuromorphic quantum computing cloud", DynexSolve | A WIPO patent (14 Nov 2024); market cap USD 119,386, down 99.9 percent; a "phase-out of the utility token" toward a securities structure | A checkable claim: no public code shows the chain checking that a QUBO instance was solved | The useful-work claim was never made verifiable | Unknown, treat as not verified | Not sampleable |
|
||||
| 11 | Qubic | 2024 to 2025 "useful PoW" (Aigarth training); from Jun 2025 the work was Monero RandomX hashing sold for QUBIC buybacks | Under 2 percent of Monero in May 2025, over 25 percent by late Jul, 51 percent claimed 11 to 12 Aug 2025; 63 of 122 blocks (3,475,729 to 3,475,850); 6-block reorg 12 Aug (about 60 orphans); 18-block reorg 15 Sep 2025 (117 transactions, past Monero's 10-block lock) | Any verification of the AI claim; no Monero consensus change by Sep 2025; miners moved to P2Pool, exchanges raised confirmations | The "useful" work was a token buyback funded by attacking another chain | The RandomX part: yes, and useless by design. The AI part: no | The AI part: no |
|
||||
| 12 | Bittensor subnets | 2023 onward, validators score miners off-chain, Yuma consensus; dTAO 13 Feb 2025 | Over 120 subnets (Mar 2026); 24 with commercial income, USD 28 M to 35 M ARR estimated (Sep 2026); validator reward correlates with stake at 0.80 to 0.95, miner reward with performance at 0.10 to 0.30 (64 subnets, 6.66 M events); weight copying fixed by commit-reveal (May 2024); Quasar (SN24) collapsed Aug 2026 on a plagiarised model | Proof of anything; usefulness is judged, not proved | By design: a stake-weighted reputation market with emission attached | No (judged) | Partly (validators pick queries, no nonce, no hardness knob) |
|
||||
| 13 | Proof of inference, 2024 to 2026 | Gensyn Verde (refereed delegation, about 60 percent overhead on Llama-3.1-1B, needs one honest provider); Hyperbolic proof of sampling (re-execute a fraction, slash); Ambient proof of logits (claimed 0.1 percent overhead, testnet still pending Apr 2026); Inference Labs (self-reported 950 M proofs); zkML (hundreds of seconds per query for billion-parameter models); Boundless PoVW (pays ZKC per proved cycle, a sample proof 2 to 5 min at USD 0.04 to 0.17); Succinct (over 6 M proofs in 2025, 25 provers) | Proving networks exist and are run by a few dozen operators | A job that is both random and wanted; a verifier cheaper than a referee, a chip or a slashing game | zk proving costs 10^3 to 10^4 times the inference; the cheaper schemes trust something | zk: yes at 10^3 to 10^4 overhead. The rest: only by trust | zk: yes if nonce-bound. Nobody has made the job random and wanted |
|
||||
|
||||
The 2025 to 2026 literature says the same thing from three sides. The SoK (eprint 2025/1814, over 50 constructions): "PoUW is actually not as useful as expected, since the economic and societal utility do not contribute to the security budget". Pass (arXiv 2606.06700): a majority attack still costs the block reward. Basu (arXiv 2606.04819): Pearl's 24 EH/s network of about 320,000 GPU-equivalents at about 112 MW "produces zero useful AI computation".
|
||||
|
||||
### 1.2 The pattern, and what Igneum finishes
|
||||
|
||||
Every programme that passed both tests (cheap verify, sampleable with tunable hardness) did so by making the work useless: Primecoin chains, Chia tables, Aleo's synthesis puzzle, Filecoin's capacity sectors, Boundless cycles. Every programme that kept the work useful gave up verification: Gridcoin, Curecoin, Bittensor, Flux, Akash, Qubic, Dynex. Filecoin alone held both for a real good and paid with hours of sealing per 32 GiB and a network two thirds filler.
|
||||
|
||||
**What Igneum already finishes (two rows).**
|
||||
|
||||
| Row | The unfinished promise | Igneum's finished form | Evidence in the repo |
|
||||
|---|---|---|---|
|
||||
| 4, Aleo PoSW and Proof of Necessary Work | Energy that produces succinct proofs of real state, paid from issuance | Every block is proven by miners in chunks and aggregated 20 to 60 s behind the tip; the provers are paid 20 percent of emission by sortition by weight; the proof is of the chain's own execution, not a synthetic circuit | CLAUDE.md design paragraph; lane 4's finding 1: the proof verified in consensus is the fix for the one line where a majority earned more than it spent (11,636 IGN an hour at 51 percent of blocks); switch `proving_consensus_verify_daa` ships off by default in 0.3.16, activation a decision owed |
|
||||
| 13, Boundless PoVW and Succinct | Pay per verified unit of proving, meter the cycles, let a market set the price | pgas cycle metering through the proving share; external jobs 90/10 provers and burn, priced in dollars, settled in the token | frontier.md 3.10 "PoVW-like cycle metering through pgas"; CLAUDE.md fee lines |
|
||||
|
||||
**Why the split is the finished form.** The bound from `new-pow.md` 3.1 is the reason nobody closed the loop: the useful fraction of a proving-as-lottery scheme is (proving work per segment) over (network hashes per segment), 8 percent at 1 GH/s and 0.08 percent at 100 GH/s, because gas sets one and the security budget sets the other. Aleo found the same wall and decoupled on 17 Sep 2024 ("provers do not participate in consensus or produce blocks"), then had to add a stake gate in 2025 because the puzzle centralised on the fastest prover. Igneum's split carries no stake and no coupling: the lottery is a Poisson process on a random program (the Kaspa developer's objection in frontier 3.10 stays satisfied), the proving is paid by sortition by weight, and the one line where the split was NOT yet finished (a majority prover pool earning more than it spent) is closed by verifying the proof in consensus. Until that switch is on, Igneum's split is Aleo's 2024 form; with it on, it is the form Aleo could not reach without a validator set.
|
||||
|
||||
**What Igneum should leave.** Rows 1 to 3, 5, 6, 9 to 12: 0 hours. The only adjacent idea with value was taken by lane 8 as scheme C (the dataset is the keyed execution state, the class v5 candidate), which answers rows 7 and 8 by making the stored thing the chain itself, at an unchanged hash rate; it is already judged and not repeated here.
|
||||
|
||||
## 2. Group B: memory-hard and ASIC-resistance programmes that stalled
|
||||
|
||||
The chip history is done in `asic-resistance-history.md`. This section covers only what was started and abandoned, and ends each row with what Igneum's hourly random program, latency shadow and class ladder already encode, and what they do not.
|
||||
|
||||
### 2.1 The rows
|
||||
|
||||
| # | Programme | What was planned | What shipped | What was left on the table | Why it stopped |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Ethash successors | EIP-969 (David Stanfill, 3 Apr 2018): five FNV primes of Hamming weight 7 to 8 against the Antminer E3, fork at block 5,550,000 with 30 days' notice; ProgPoW as "Ethash 2" | Nothing on Ethereum. ETC's Etchash (ECIP-1099, Thanos, block 11,700,000, Dec 2020) halved DAG growth to keep 3 to 6 GB cards | The FNV tweak (Stagnant), the DATASET_PARENTS increase Least Authority asked for, any epoch recalibration on mainnet | No client team adopted EIP-969 for a chip expected to be short-lived; attention moved to ProgPoW; PoW ended at the Merge 15 Sep 2022 |
|
||||
| 2 | ProgPoW, EIP-1057 | Hold the chip gain to 1.1x to 1.2x by saturating the whole GPU datapath (random math, 16 KB cache, DAG loads, keccak-f800) | Two audits (Least Authority 9 Sep 2019: accurate to design, five suggestions, the light-evaluation attack named; Bob Rao: 100 MB on-die SRAM "possible", DRAM 3 pJ per bit against 0.3 on chip); KawPow (6 May 2020), FiroPoW (26 Oct 2021), Zano, Sero, Quai (29 Jan 2025) | Activation on Ethereum: tentatively accepted 4 Jan 2019, "accepted and final" 21 Feb 2020, petition EIP-2538 27 Feb, kik's 64-bit-seed exploit about 4 Mar, off the fork schedule 6 Mar 2020; authors reposted it 11 May 2020 saying "we do not recommend" deployment | Governance (a revival read as stealth), the PoS roadmap, a bug two weeks after "final", the authorship conflict (Core Scientific, Linzhi's 3x to 8x claim with no evidence) |
|
||||
| 3 | RandomX v2 | A tweak after seven years: program 256 to 384 instructions, 16 AES mixes in place of XOR, dataset prefetch 2 iterations ahead; work per hash up 52.9 percent (4,456,448 to 6,815,744 ops) to re-match CPU to memory latency | Library PR 317 merged 17 Feb 2026 (SChernykh); commitments PR 265 merged 8 Sep 2023 | Activation: Monero PR 10038 (v17) open since 14 Aug 2025, pending review 19 Sep 2026, no date | The chip came first: Antminer X5 Sep 2023 (212 kH/s, 1,350 W); X9 1 MH/s at 2,472 W listed as "August 2026, delayed" per resellers, unconfirmed shipping; the Qubic episode used plain CPUs so it forced no algorithm change |
|
||||
| 4 | Kaspa kHeavyHash | A hash for optical hardware (Dubrovsky, Ball, Penkovsky, arXiv 1911.05193): shift cost from OPEX to CAPEX; GPU resistance never a goal; chosen by community vote one day before launch | IceRiver KS0 (100 GH/s at 65 W, 2023), then 15 to 21 TH/s boxes; GPUs at 2.2 GH/s left | A GPU lane (never planned); optical mining (no shipping hardware, approximate) | By design. The GPU fleet forked away: Karlsen (FishHash, 4 GB DAG), Spectre (CPU), Pyrin |
|
||||
| 5 | Autolykos v2 | Keep the 2 GB table, grow it 5 percent per 51,200 blocks from block 614,400, cap at block 4,198,400 (2,143,944,600 elements) | EIP-0009 at block 417,792 (Feb 2021 approximate); non-outsourceability removed because contract pools bypassed it (Chepurnoy and Saxena, eprint 2020/044) and small miners did not want it | A successor to pool resistance; an answer to HBM FPGAs ("HBM FPGA friendly", Osprey listed ERGO); the growth stops at the cap | Small prize, no chip, the table cap is a policy choice |
|
||||
| 6 | Grin's two lanes | Cuckaroo29 for GPUs (90 percent of reward falling to 0 over two years, tweaked every six months), Cuckatoo31+ for chips (10 percent rising to 100) | Four hard forks on schedule; HF4 (about 15 Jan 2021) ended the GPU lane; one chip shipped (iPollo G1, 36 GPS at 2,800 W, Dec 2020) | Obelisk GRN1 cancelled 19 Jul 2019 ("lack of interest and funding", full refunds); Innosilicon G32 announced, never documented shipping; the lean-solver memory claim: Tromp's USD 10,000 linear TMTO bounty claimed 11 Apr 2025 (about half an order of magnitude, not tenfold) | The schedule forced chip makers to choose between a single-use C31 chip and a late C32 chip at a USD 2 coin; network today 3.40 KGps on GPUs |
|
||||
| 7 | Equihash parameter programmes | Zcash: a parameter change "ideally deployed by late 2018"; a 2020 PoW migration (ballot 3 Jul 2018: 38 of 64 for migration by 31 Dec 2020). Beam: forks at 6 and 12 months then an "affordable ASIC" (BeamHash III, 28 Jun 2020). BTG: Zhash 144,5 after the May 2018 51 percent attack (388,000 BTG) | Zcash stayed on 200,9 with chips (2019: BCTV14 forced the choice of Sapling over ASIC work); Beam stayed on BeamHash III with GPUs only; BTG forked and was hit again in Jan 2020 | Zcash's migration; Beam's affordable chip; BTG's real problem (rented hash on a small chain) | Governance by poll chose "reduce chokepoints" over resistance; parameter forks do not fix a rentable chain |
|
||||
| 8 | Octopus to Ethash | CIP-102 (Chenxing Li, 5 Aug 2022): switch to Ethash "to make it easier for Ethereum miners to switch to Conflux" | Nothing; the CIP has "TBA" for specification and rationale and never left Draft | The AMD support and DAG shrink the forum asked for instead | The community called Ethash "not ASIC resistant" and Octopus "the trademark" |
|
||||
| 9 | Blake3 on Alephium | ASIC friendly by design, "just like Bitcoin" | Goldshell AL-BOX (360 GH/s, May 2024 per the aggregator), Antminer AL1 (15.6 TH/s, Jul 2024); the project's own gpu-miner and fpga-miner repos stopped mattering | Nothing, by the project's own view | By design |
|
||||
| 10 | Tensor PoW | Komargodski and Weinstein, eprint 2025/685: PoUW from arbitrary matrix multiplication at 1+o(1) overhead; "this blockchain is currently under construction" | A paper (15 Apr 2025, revised 8 Dec 2025). ethresear.ch has no topic matching "tensor core proof of work" | A chain; a measurement of what tensor work costs the honest card against a chip | Nobody measured it before writing it |
|
||||
|
||||
### 2.2 The FPGA question, one line per row
|
||||
|
||||
| Row | Was the FPGA the first adversary | What stopped it |
|
||||
|---|---|---|
|
||||
| Lyra2REv2 (Vertcoin) | Yes: a Zynq UltraScale+ at 31.25 MH/s and 24.93 W, 4.3x a Titan Xp per joule (arXiv 1905.08792) | A fork to Lyra2REv3 |
|
||||
| X16R (Ravencoin) | Yes: all 16 hashes on one UltraScale+, about 5x a 1080 Ti per joule at 3 to 4x the price | X16Rv2 then KawPow |
|
||||
| Equihash 144,5 and 200,9 | Promised in 2018 (a vendor asked a USD 1,000,000 minimum order), no hashrate ever posted | The 2.5 GB table; HBM boards matched GPUs per dollar at best (approximate) |
|
||||
| Ethash and ProgPoW | HBM boards existed in 2019; Least Authority: a 10-block period makes "building and loading a bitstream in such a short period (about 2 minutes) impractical" | DRAM latency on cheap boards, HBM cost on fast ones, bitstream churn |
|
||||
| Autolykos v2 | Osprey listed ERGO on an 8 GB HBM board; no share figure published | Nothing stopped it; nothing measured it |
|
||||
| kHeavyHash | Osprey E300, 14 GH/s at 250 to 500 W, USD 4,999, three VU35P with HBM2, announced 13 Dec 2022 | Overtaken within months by 100 GH/s to 1 TH/s ASICs |
|
||||
| RandomX, Cuckoo | No FPGA product ever shipped | A CPU-like core with 2 MB per thread; 512 MB to 1 GB of fast memory per graph |
|
||||
|
||||
### 2.3 What Igneum already encodes, and what it does not
|
||||
|
||||
| Row | Encoded in Igneum (where) | Not encoded (the gap) |
|
||||
|---|---|---|
|
||||
| 1 Ethash successors | A dataset that grows on a genesis-fixed schedule with the cache doubling (256 MiB, 512 MiB year 4, 1 GiB year 12); the 4 GB cliff lesson lives in the tier rule ("every number carries its consequences", 8 GB to 32 GB cards); EIP-969's human tweak against a chip is replaced by automatic era draws | The DATASET_PARENTS idea is Igneum's mixer x8 (8 dependent reads per item), which has the fixed shape the chip history calls the 3x factor; the random item derivation (asic-history addition 2) is still open |
|
||||
| 2 ProgPoW | The governance death is designed out: every layer is a genesis rule or a reserve, no adoption vote (asic-history lesson 7); the 64-bit-seed class: Igneum's seed is 256 bits and the program is the epoch's; the light-evaluation attack is priced (chip-model-v3, 0.31x bare, 0.92x with the 3x factor); the datapath target is the whole GPU | ProgPoW's 2-minute period was its FPGA defence; Igneum's epoch is 1 h, so a soft-overlay bitstream within the hour is the open lane (asic-history addition 5, the 10 min to 2 h epoch reserve, design on `ca2-epoch`) |
|
||||
| 3 RandomX v2 | The lesson is that a tweak after seven years arrives after the chip. Igneum draws era parameters every 6 months from chain state and unlocks families by height, with no human release; the class ladder (95 percent with a floor height, P2) is the only human path and it is for new code, not for parameters | Per-hash program entropy (RandomX's 25x GPU cost, refused on purpose); the random superscalar item derivation; four external audits before launch (RandomX paid USD 141,000; Igneum's cryptanalysis spend of USD 80,000 to 160,000 is a decision owed) |
|
||||
| 4 kHeavyHash | The loss of the GPU fleet is the metric: hashrate share by card model from the observer and the January 2027 benchmark (asic-history addition 4) | A published vendor-share number does not exist yet |
|
||||
| 5 Autolykos v2 | Pools are wanted (spec 09), so non-outsourceability is nowhere by choice; the dataset grows by rule | Whether Igneum's growth schedule ever caps is a spec 1.13.3 question this lane did not reopen |
|
||||
| 6 Grin | Scheduled change without a vote is the shared idea; Igneum's draws are automatic, Grin's were forks | The lean-solver lesson (a memory claim weakened six years later by a bounty) is the reason the mixer and chained cache need cryptanalysis (ledger M7) |
|
||||
| 7 Equihash | The rented-hash problem is answered by the 30-day weight finality, not by the hash (section 4) | Nothing |
|
||||
| 8, 9 Octopus, Blake3 | Nothing to take | Nothing |
|
||||
| 10 Tensor PoW | Measured and retired: scheme B's mm8 shadow costs the honest card 0.056 to 0.70 pJ per multiply-add and moves the chip's edge only from 6.9x to 4.9x, so it is reserve R8 with the two-output correction (new-pow.md 6) | Nothing; Igneum did the measurement the 2025 paper has not |
|
||||
| FPGA, all rows | Bitstream churn is encoded as the hourly program; the HBM board is the f = 1 stored-dataset chip in the model (5.1x on GDDR7, 7.5x to 9.2x on HBM3) | The soft-overlay FPGA miner with HBM is unmeasured (addition 5); no row in the chip model for a partial-store chip (addition 1) |
|
||||
|
||||
Verdict for the group: nothing in B is a programme Igneum should restart. The three unfinished items that do matter are Igneum's own open rows (random item derivation, external cryptanalysis, the epoch-length and FPGA-overlay measurement) and they are already ranked in `asic-resistance-history.md` 4.3.
|
||||
|
||||
## 3. Group C: merged mining and multi-algo
|
||||
|
||||
### 3.1 The rows
|
||||
|
||||
| # | Programme | When, by whom | What it bought (numbers) | The attacks and the costs | Left undone |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Namecoin AuxPoW | Block 19,200, Oct 2011 (vinced; Mike Hearn's outline) | Bitcoin's hashrate for free: about 95 percent of Bitcoin on 12 Jan 2020, about 69 percent (732 EH/s) in Sep 2026; nine pools find blocks (AntPool 34.78 percent, F2Pool 19.99, ViaBTC 19.69, 14-day window to 20 Nov 2025) | One pool (F2Pool) above 50 percent of Namecoin for most of late 2014 to 2016 (approximate; Judmayer et al.: pools "operated at the edge of, and even beyond, the security guarantees" of Nakamoto consensus); the spec's nonce-clash flaw is documented and a replacement BIP never came | Independence: the child's rules are signalled by the parent's pools; Foundry, the largest Bitcoin pool, does not merge-mine it; the name market never grew |
|
||||
| 2 | Dogecoin on Litecoin | Hard fork at block 371,337, Sep 2014, after Charlie Lee's Apr 2014 proposal; Dogecoin's own words: "our hashrate has been on a decline" | Hashrate up over 1,500 percent in Sep 2014; DOGE and LTC hashrates correlate at 0.95; about 90 percent of DOGE hashrate from Litecoin pools by 2019; DOGE's share of pool revenue "has long surpassed" Litecoin's (F2Pool data, 7 Apr 2026) | A pool above 50 percent of Litecoin is above 50 percent of Dogecoin by construction | A miner base of its own; any say in the parent's rules |
|
||||
| 3 | Myriadcoin and Verge multi-algo | Myriad 23 Feb 2014 (five algos, per-algo floating difficulty, 20 percent of blocks each); Verge (five algos) | Hardware diversity | Verge, Apr 2018: timestamps spoofed about an hour back on the Scrypt lane, per-lane difficulty collapsed, blocks seconds apart, 250,000 to about 20 M XVG (reports conflict); 22 May 2018: Scrypt and Lyra2re both at near-zero difficulty, about 25 blocks a minute, 35 M XVG (about USD 1.8 M); the hard fork between them did not remove the bug; a 560,000-block reorg in Feb 2021 (headline only) | Five lanes made the surface five times wider; each lane is cheaper to rent than the whole |
|
||||
| 4 | DigiByte MultiShield, Odocrypt | DigiShield Feb 2014 (block 67,200), MultiAlgo Sep 2014 (145,000), MultiShield Dec 2014 (400,000), Odocrypt Jul 2019 (9,112,320): per-block retarget on all five lanes from a 10-block average, clamps 16 percent down and 8 percent up; Odocrypt rewrites itself every ten days for FPGAs | DigiShield was adopted by Dogecoin (1.6, Mar 2014) and Zcash (v3 variant, approximate); the "93 percent on one algo and 51 percent on the other four" claim is a model, not a measurement | 28 Jun 2026: a dropped validation rule on the Groestl lane let about 1,356 blocks (about 351,000 DGB) be mined, no double spend | A lane with little hashrate still mints one block in five; the ten-day rewrite assumes FPGA owners re-flash and does nothing against a party with many FPGAs |
|
||||
| 5 | Elastos, RSK | Elastos with Bitmain 28 Aug 2018 (AntPool, BTC.com); RSK since Jan 2018 (approximate) | Elastos "over 50 percent" of Bitcoin's work (13 Mar 2024); RSK 62.46 percent (Q1 2024), 54.58 (Q3), 56.40 (Q4 2024, AntPool 38.6, ViaBTC 24.3, F2Pool 16.9); Foundry joined | RSK's Armadillo (RSKIP110): detects forks up to about 448 blocks when the attacker has under 9 percent of Bitcoin; "cannot stop a miner creating a blockchain from a past point"; the PowPeg is a federation with HSMs | The attacker rents or bribes three pools, not hardware; Armadillo alerts, it does not finalise; usage stayed small so fees stayed small |
|
||||
| 6 | The paper | Judmayer, Zamyatin, Stifter, Voyiatzis, Weippl, "Merged Mining: Curse or Cure?", eprint 2017/791 | One line: merge-mined chains concentrate in fewer pools than the parent, with single pools above 50 percent for months in Namecoin, Huntercoin and Myriad (approximate for the per-coin detail) | | |
|
||||
|
||||
### 3.2 Verdict: does Igneum want any of it
|
||||
|
||||
No, with four reasons. Igneum's ruling is already "no reliance on another chain"; the question left is whether an Igneum-side auxiliary chain or an "algo slot" has value, and it does not.
|
||||
|
||||
| Option | What it would buy | Why not | Hours |
|
||||
|---|---|---|---|
|
||||
| Igneum as a merge-mined child of another chain | Borrowed hashrate | Ruled out (CLAUDE.md: no other chain in consensus); and the rows show the child inherits the parent's pool concentration and loses its say in rules | 0 |
|
||||
| Igneum as a parent for other chains | Nothing for Igneum; a coinbase commitment slot for others | The hash is a new program every hour, so a child cannot verify Igneum work without Igneum's program pipeline, VDF seed and dataset; the vote key per operator would not carry to the child; the only cost is a header field nobody asked for | 0 until asked |
|
||||
| An algo slot (a second hash lane) | Hardware diversity | Myriad, Verge and DigiByte show each lane is a separate difficulty controller and a separate rentable surface; Igneum's 30-day vote weight is counted in blue blocks of ONE work unit, so a second lane splits the weight table and the DAA; GHOSTDAG's k is derived from one block rate on one hash | 0 |
|
||||
| A share sidechain merge-mined through the coinbase | A pool without an operator | This is the one merged-mining shape with value, and it belongs to section 5 (it is how Monero P2Pool works) | see 5.3 |
|
||||
|
||||
## 4. Group D: GPU-friendly finality attempts
|
||||
|
||||
The one axis asked: can a renter buy or break finality in a day? Igneum's rule for comparison (CLAUDE.md, finality rule v2): vote weight = blue blocks per vote key over a flat 30-day window, lock at 2/3 of all 30-day weight, so 10 days of 100 percent hashrate reach 1/3 of weight and 20 days reach 2/3; 51 percent never reaches 2/3 while honest miners stay.
|
||||
|
||||
### 4.1 The rows
|
||||
|
||||
| # | Programme | Attempted | Finished | Left undone | Renter buys finality in a day? |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Decred hybrid | Feb 2016: tickets vote on every block, 3 of 5 called tickets must sign; 40,960 ticket pool, 28-day average wait | A PoS veto on every block, stakeholder-voted forks, a treasury; stake locked rose from 50 percent (Dec 2019) to 64 percent (Dec 2022) while hashrate fell about 6x; DCP-0011 (BLAKE3 and ASERT) and DCP-0012 (PoW to 1 percent of subsidy) active at block 794,368 (Aug 2023) because PoW was "used to maliciously manipulate Decred markets" | No finality gadget; PoW is a 1 percent stipend | No for reorgs (needs about half the live ticket pool, roughly 30 percent of DCR bought over 28-day cycles, approximate); yes for withholding and censorship, since the hash layer is thin |
|
||||
| 2 | Horizen delayed-block penalty | After the 2 Jun 2018 51 percent attack (over USD 500,000, 36 fake blocks): a block n late owes about n(n-1)/2 extra blocks (approximate formula); shipped in ZEN 2.0.16, late 2018 | A reorg tax on hidden chains | Partition safety: the smaller honest side looks like a hidden chain; Horizen now calls itself an L3 on Base and the PoW mainchain is winding down (approximate) | Yes under 5 blocks and for public mining; a deep secret reorg costs quadratically more. A tax, not finality |
|
||||
| 3 | Kaspa merge depth and finality depth | MERGE_DEPTH_DURATION 3,600 s, FINALITY_DURATION 43,200 s, PRUNING 108,000 s in rusty-kaspa constants; at 10 BPS since Crescendo (about 5 May 2025): merge depth 36,000 blocks, finality depth 432,000 | Online nodes refuse a reorg deeper than 12 hours; a block cannot merge a side chain older than an hour | Both rules are local and subjective; a node syncing later follows the heaviest DAG; DAGKnight (eprint 2022/1494) is in PRs (1104, 1124, 1132) and not on mainnet | Partly: under 12 hours yes with over 50 percent of an ASIC-only hash (thin rental supply); over 12 hours the attacker splits the network instead |
|
||||
| 4 | Conflux Tree-Graph, GHAST, PoS votes | GHAST (arXiv 2006.01072): confirmation in O(d log 1/epsilon), about 3d against about 360d for six Bitcoin blocks; adaptive weight switches to a conservative mode under a liveness attack | CIP-43 (Hydra, about 28 Feb 2022): a PoS chain of staked CFX (1,000 CFX per vote) votes on pivot blocks "to protect against 51 percent attacks from PoW" | GHAST alone is probabilistic; the PoS committee is the finality | No for finalised pivot blocks; yes inside the trailing minutes |
|
||||
| 5 | Ethereum Casper FFG over PoW | EIP-1011 (20 Apr 2018): 1,500 ETH minimum deposit, 50-block epochs, "never revert finalised blocks"; testnet 31 Dec 2017 | Nothing on mainnet; the Berlin Eth2 workshop (Jun 2018) discarded hybrid Casper for the beacon chain | The hybrid itself | Under the design: no for finalised checkpoints (slashing two thirds of deposits), yes inside an epoch. Dropped because "the limited bandwidth of the EVM constrained the size of the validator set", forcing whale-sized deposits |
|
||||
| 6 | Bitcoin Cash Avalanche pre-consensus | Sechet, Sep 2018 (approximate) | On BCH: nothing. On eCash (XEC): post-consensus finality 14 Sep 2022, staking rewards 15 Nov 2023, pre-consensus 15 Nov 2025 at block 923,347 ("finalise transactions in under 3 seconds"); staked XEC 69.4 B to 242 B in the year to Jul 2024, 350 B+ by Jul 2026; 33 to 94 nodes; 100 M XEC minimum per stake UTXO with 2,016 confirmations | BCH never got it; no slashing (approximate); on quorum loss the chain falls back to heaviest work | No while the quorum holds; yes if the attacker knocks the about 94 Avalanche nodes offline |
|
||||
| 7 | Ethereum Classic MESS | ECIP-1100, active at block 11,380,000 (Oct 2020) after three Aug 2020 reorgs (3,594, 4,446 and 7,278 blocks; 807,000 and 465,444 ETC double spent): a capped polynomial, up to 31x the local chain's difficulty for a segment older than about 7 hours | No deep reorg on ETC after Oct 2020 (approximate) | Deactivated by ECIP-1110 at block 19,250,000 (Spiral, Jan 2024) because "the costs now outweigh the risks"; Core-Geth v1.13.0 (15 Sep 2026) re-enabled it with 96 unreviewed commits, miners rolled back the same day, PR 53 (merged 6 Oct 2026) restored the deactivation | With MESS on: no for reorgs older than 7 h, yes for short ones, and an eclipse strands a victim. Off today: yes, plain PoW |
|
||||
| 8 | Zcash trailing finality layer, Crosslink | ECC's TFL book (2023, Wilcox and Hopwood); Shielded Labs builds zebra-crosslink, "a proposed upgrade"; BFT PoS finalises a trailing prefix, PoW continues if BFT stalls | An incentivised feature net; milestone 4 of 5 in early 2026 (search summaries) | Mainnet: nothing by 7 Oct 2026 | Today: yes (plain Equihash, rentable, approximate). Under Crosslink: no for finalised blocks, yes inside the trailing window |
|
||||
| 9 | Nervos CKB | NC-Max (eprint 2020/1101): two-step confirmation, 3.0 to 6.6 times shorter latency; Eaglesong "ASIC neutral", first chip four months after launch | NC-Max runs since launch | No finality layer; no hybrid plan | Yes in principle; ASIC-only supply is thin |
|
||||
| 10 | Dash ChainLocks, Syscoin, Komodo | Dash DIP-0008 since block 1,088,640 (Jun 2019): an LLMQ of 300 to 400 masternodes signs the first block seen, 240 signatures lock it; Syscoin copied it over Bitcoin merged mining (4.2.0, block 1,004,200, 30 Apr 2021); Komodo notarises to Bitcoin then Litecoin every 10 to 25 minutes through 64 elected notaries | Finality in about one block (Dash) | A second validator set bought with coins (1,000 DASH per masternode, approximate) or elected (Komodo's 64) | No for locked blocks |
|
||||
|
||||
### 4.2 The comparison on the one axis
|
||||
|
||||
Every finality on a PoW chain that holds in practice is a second validator set paid in coins: Decred tickets, Dash and Syscoin masternodes, eCash stake, Conflux PoS, Komodo notaries, Casper FFG's 1,500 ETH deposits. Every rule that only reweights work (Horizen's penalty, MESS, Kaspa's 12-hour depth) is subjective and partition-sensitive, and two of the three have been switched off or are winding down.
|
||||
|
||||
Igneum is the only design in the table whose second set is the miners themselves, weighted by past blocks, with no coins staked. On the one axis:
|
||||
|
||||
| Attacker | Decred | Kaspa | MESS (on) | eCash | Igneum |
|
||||
|---|---|---|---|---|---|
|
||||
| Rents 100 percent of hashrate for one day | Withholds, cannot reorg | Reorgs up to 12 h | Reorgs up to about 7 h | Nothing while the quorum holds | Reorders inside the 90-second window (horizon 1, finding 3), nothing past a certificate; has 1/30 of a window's weight at the end of the day, needs 2/3 |
|
||||
| Rents 100 percent for 20 days | Same | Splits the network | Same as above | Same | Reaches 2/3 of weight only if every honest miner has left; with honest miners staying, 51 percent never reaches 2/3 |
|
||||
| Buys coins | Buys tickets over 28-day cycles | n/a | n/a | Buys stake | Nothing; weight is not for sale |
|
||||
|
||||
What is unfinished in Igneum's own finality is Igneum's, not the industry's: the signed LEAVE item (0.3.16) after the 6 October pause (42.7 percent of the frozen voter table left in three minutes and the pause held a window), the 10-percent-per-hour removal rule, weight-gated deep fork choice and vote-or-burn (horizon 1, findings 2 and 3). The one lesson this group adds: every subjective reweighting rule was eventually turned off by its own chain (MESS twice), so the "no hidden-block n^2 penalty" removal in Igneum's rule v2 was the right side of the history.
|
||||
|
||||
## 5. Group E: decentralised pools
|
||||
|
||||
### 5.1 The rows
|
||||
|
||||
| # | Programme | When, by whom | Design | What it achieved (numbers) | Why it stalled or died | Left undone |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Bitcoin P2Pool | Forrest Voight, 2011 | A share chain with a 30-second share period; window = shares worth 3 blocks of work or 8,640 shares (72 hours), whichever is smaller; the generation transaction pays the window; 0.5 percent to the finder, no operator | 350 GH/s in Apr 2012 (about 2 to 3 percent of Bitcoin, approximate); 1.4 TH/s by Jul 2013; today 915 TH/s on 14 payout addresses (about 0.0001 percent of about 950 EH/s, my division); last commit 19 Sep 2018 | Variance for small miners (a small miner rarely lands a share in the window), dust (one output per miner per block), a 20x faster chain so stales are "common and expected", a 2012 orphan flaw, a Python 2 node on a full bitcoind | Payout aggregation, per-miner vardiff inside the chain, a maintained node (c2pool's C++ rewrite says "v37 is design-stage, do not run in production") |
|
||||
| 2 | Monero P2Pool | SChernykh, Aug 2021 | A sidechain merge-mined with Monero: 10-second blocks, PPLNS up to 2,160 blocks (6 hours), uncles at 20 percent less, payouts in the Monero coinbase, 0 percent fee, about 0.00027 XMR minimum, every miner runs monerod; three chains (main, mini, nano since v4.7, 30 May 2025); Tari merge mining since the 12 Oct 2024 fork | Today: main 441 MH/s and 561 miners, mini 25.3 MH/s and 1,997, nano 4.8 MH/s and 962; 471 MH/s and 3,520 miners in all, 7.7 percent of 6.13 GH/s (my division); against SupportXMR at 2.35 GH/s (38 percent) and HashVault at 672 MH/s (11 percent); the Monero GUI bundles it | A local node is mandatory; below about 15 MH/s of pool hashrate "not all shares will result in payouts"; three chains fragment the hashrate; share has fallen from double digits (approximate) to 7.7 percent | A light mode; cross-chain vardiff; payout smoothing for mini and nano miners |
|
||||
| 3 | Stratum V2 | Braiins (Moravec, Capek) with Matt Corallo, 2019, from Corallo's BetterHash draft (12 Mar 2018, never past specification) | Binary, Noise-encrypted; Mining, Job Declaration (renamed from Job Negotiation) and Template Distribution sub-protocols; the SRI translation proxy | SRI v1.0.0 tag 23 May 2023, v1.12.0 17 Sep 2025; native V2 pools: Braiins (JD "since about 2020"), DEMAND (public Nov 2025), ckpool (31 Jul 2026); firmware: Braiins OS, Bitaxe ESP-Miner v2.14.0 (Jun 2026), Auradine; stock Antminer and WhatsMiner are V1 only; the first known Job Declaration block is 955,318 on 25 Jun 2026 (DMND, GoMining's template); seven pools holding about 75 percent of hashrate joined the working group on 7 May 2026 "still in testing" | No primary number for SV2 hashrate; the two V2-native pools mined 753 and 1 of 52,297 blocks in the last year, so under 2 percent on SV2 at all and far under 0.1 percent on JD (approximate conclusion) | Stock firmware, JD at any top-five pool, Windows template building ("not yet supported"), a payout scheme that rewards miner-built templates, any measurement of adoption |
|
||||
| 4 | Braiins Pool | Slush Pool, Nov 2010; the first anti-hopping score (Rosenfeld 2011, section 3.1) | FPPS "0 percent fees with Braiins OS", 2 percent otherwise; on-chain or Lightning payouts | 13.91 EH/s, 177,359 workers, 13,112 users today; 753 of 52,297 blocks (1.44 percent) in the last year | The oldest pool and the SV2 author holds 1.4 percent | SV2 adoption did not become hashrate |
|
||||
| 5 | Ocean | Luke Dashjr, 28 Nov 2023, USD 6.2 M seed led by Jack Dorsey | TIDES: a share log eight times the difficulty deep, each share rewarded about eight times, 99.9665 percent chance of at least once, paid in the coinbase itself; DATUM (29 Sep 2024): the miner runs a gateway and a full node and builds its own template, 50 percent fee discount (2 percent, 1 percent with DATUM) | First block's coinbase came from a test server and paid nobody (4 Dec 2023 post-mortem); today 28.97 EH/s, 2,522 users, 1,553 blocks of which 1,316 DATUM (85 percent, my division), 1,073 of 52,297 blocks (2.05 percent) in the last year; Tether committed hashrate Apr 2025 | The template fight: Bitcoin Knots filtered inscriptions and Samourai transactions at launch; Dashjr resigned 29 Aug 2026 after the "mining divide" | A single operator still runs TIDES accounting, share verification and the generation transaction; non-DATUM miners get the pool's policy |
|
||||
| 6 | DEMAND | Guru Protocol Ltd (company 15235937), Alejandro De La Torre; launched Mar 2025 on the SRI, public Nov 2025 | "The world's first Stratum V2 mining pool"; SLICE pays subsidy by hashrate and fees "by the value you built"; Rootstock merge mining | 1 block in 19 months | Scale | SLICE at size |
|
||||
| 7 | Kaspa pools | Today: 334.9 PH/s; F2Pool 98.14 PH/s (29.3 percent), HumPool 92.95 (27.8), ViaBTC 37.89 (11.3), K1Pool 16.74 (5.0), WhalePool 13.67 (4.1); top two hold 57 percent | All PPLNS or PPS+ with 0.5 to 4 percent fees | No non-custodial or P2Pool-style pool; solo bridges only (K1Pool Solo, Kaspa-Pool SOLO, HeroMiners SOLO); no pool page explains which DAG blocks pay (approximate: blue blocks in the mergeset, red blocks earn nothing) | n/a | A decentralised pool on a DAG chain has never been built |
|
||||
| 8 | SmartPool | Luu, Velner, Teutsch, Saxena, eprint 2017/019, USENIX Security 2017 | Shares in batches ("1 transaction can claim 1 million shares") in an augmented Merkle tree; the contract samples k random paths; a caught cheater is paid nothing | ETC first ("over 30 blocks"), Ethereum closed beta Jun 2017; 105 blocks across both, peak 30 GH/s from 2 miners (about 0.05 percent of Ethereum, approximate); cost 0.6 percent of block rewards in transaction fees | Gas prices rose about ten-fold in 2017 and the claim cost passed the fee it was meant to beat; the beta never opened; the team founded Kyber; PoS removed the target (all approximate: smartpool.io is dead) | Any successor with hashrate; the opposite approach (Autolykos v1's non-outsourceable puzzles) was bypassed with collateralised contracts (eprint 2020/044) and removed |
|
||||
| 9 | Non-custodial on GPU chains, 2024 to 2026 | Ravencoin: 2Miners 292.67 GH/s with "51 percent of recent blocks", Hiveon 621 GH/s, no P2Pool; Ergo: 2Miners 303.14 of 491.76 GH/s (61.6 percent), no P2Pool; SoloPool.org sells solo mining at 1.5 to 2 percent "no pool wallet, no balances" | | Outside Monero every "non-custodial" offer is solo with a stratum bridge, not pooled variance reduction | | |
|
||||
|
||||
Share verification cost, from the sources: a Scrypt proxypool spends "around 50 percent of the server's CPU time" checking shares; RandomX light mode on an i9-9900K verifies 1,160 H/s on 16 threads, so a core checks about 70 shares a second (fast mode about 700); Bitcoin SHA256d is microseconds per share (approximate, 10^5 to 10^6 per core). Igneum's pool v0 checks every share on the CPU warp verifier at 1.35 ms isolated and 2.1 ms under load, 480 to 740 per core (`pool.md` section 5), which is RandomX-fast-mode class, 1,000x a SHA256 share.
|
||||
|
||||
The payout pattern: FPPS won on Bitcoin because large pools carry variance and sell certainty (Foundry about 30 percent, AntPool about 19, ViaBTC 14, F2Pool 10, d-central 2026); every non-custodial design (P2Pool, Eligius CPPSRB, TIDES, SLICE) is PPLNS-shaped because a coinbase cannot pay a debt. That is the structural reason non-custodial pools stay small, and it is the reason Igneum's pool is PPLNS.
|
||||
|
||||
### 5.2 What Igneum's pool lacks against the best of these
|
||||
|
||||
Igneum's pool v0 (`docs/plans/pool.md`, spec 09): newline JSON over plain TCP, mode A templates, every share CPU-verified, PPLNS over 2 blocks of weight, 1 percent fee, payouts by EIP-1559 transfer from the pool's coinbase key after blue confirmation, `state.json` every 15 s, the member's vote key in every header it hashes, the pool holds no key, the member checks seeds, class and era against its own node.
|
||||
|
||||
| Axis | Best in the field | Igneum v0 | The gap |
|
||||
|---|---|---|---|
|
||||
| Operator trust for payout | Monero P2Pool and TIDES pay in the coinbase itself; nobody holds a balance | The operator holds the 80 percent in its coinbase address and pays by transfer at most 16 per round; a crash loses up to 15 s of shares; a dishonest operator can misaccount or withhold | The whole of the trust. Mode C (declared templates) and vote carriage are designed, not built; neither removes the operator from payout |
|
||||
| Operator trust for template and vote | DATUM and SV2 Job Declaration let the miner build the template; neither touches governance | The member's vote key rides in every header (no Stratum pool does this); mode C is designed, not built | Transaction choice is the pool's in mode A; the vote is already the member's |
|
||||
| Variance | P2Pool main: 6-hour window, 561 miners; Ocean: a window 8 difficulties deep | PPLNS over 2 blocks of weight at 1 block a second: a 2-second window of work. Each share is 2^-s of a block, so a 100 MH/s card at one share per 10 s is paid on every block it has a share in | The window is short because blocks are frequent; variance is a block-time question, not a pool question, and v0 has no number for the income variance of a 100 MH/s member at devnet scale |
|
||||
| Share verification | Full recompute everywhere; sampling at high difficulty (spec 9.8 item 5, allowed above shift 8, not built) | 1.35 to 2.1 ms per share, one core per 480 to 740 shares a second, no sampling | One template fetch per member per second is the real limit: 1,000 members is 1,000 `getBlockTemplate` calls a second and 10 MB/s (pool.md section 3) |
|
||||
| Transport | SV2 Noise encryption, binary framing | Plain TCP, `binding` sent empty, against spec 9.3's TLS 1.3 | Q67 (polish 3.6): 8 hours |
|
||||
| Stats and alerts | 2miners charts, offline alerts, luck | 15-minute samples not persisted, no `/metrics`, no payout history on the page, luck defined inverted | Q68 to Q73: about 17 hours |
|
||||
| Finality | No pool in the field has a finality concept | Pays on blueness, says nothing during a pause | Q73: 1 hour |
|
||||
|
||||
### 5.3 "Shares as first-class protocol objects, no operator": already in Igneum, partly
|
||||
|
||||
| Piece | In Igneum already | Where | What is new |
|
||||
|---|---|---|---|
|
||||
| Governance weight follows the hasher, not the pool | Yes: the vote key per operator in the header (spec 9.6), pool concentration is not vote concentration | spec 2.2 fork point a5, spec 9.6 | Nothing; this is further than SV2 or DATUM went |
|
||||
| An operator-less payout by weight | Yes for the 20 percent: the proving share is paid by sortition by weight from consensus data, no operator | CLAUDE.md, `executor.rs` (pool.md "The 20%") | Nothing for provers |
|
||||
| A share the chain can see | No: a share is a pool message, verified by the pool, paid by the pool | spec 9.8 | This is the new thing |
|
||||
|
||||
The finished form is Monero P2Pool's, not SmartPool's. SmartPool's on-chain claims died on gas at Ethereum's share rate; at Igneum's 1.35 ms per share verification, a contract that samples k paths per million-share claim would still need the zkEVM to re-derive the program's warp unit per sampled share, which is the one cost the design keeps off the chain. A share sidechain merge-mined through the coinbase extra data is the shape that works: Igneum's coinbase already carries the member's key reveal and the pool's address (pool.md section 2), so the commitment slot exists.
|
||||
|
||||
| Finish | What it is | Hours (agent) | Gate |
|
||||
|---|---|---|---|
|
||||
| A P2Pool-style share sidechain in the pool crate | Shares form a 10-second chain committed in the coinbase extra data; the window's payout is the block's own coinbase split (the execution layer already credits by rule from consensus data for the 20 percent, so the 80 percent split follows the same path); no balance, no operator key; every member runs a node (the design already assumes one, spec 9.1) | about 40 (sidechain 16, coinbase split in the executor 8, member client 8, Devnet 2 crossing 8) | a 3-node fast-time network pays a window with no operator; an operator that drops every share from one member cannot stop that member's payout |
|
||||
| Mode C declared templates | The miner builds the block (DATUM, SV2 JD) | 6 (O-9.4 RPC 2, messages 4) | a declared template is paid like any other |
|
||||
| TLS and the `binding` field | spec 9.3 | 8 (Q67) | a replayed `authorize` on a second connection is refused |
|
||||
|
||||
The verdict for the group: finish the share sidechain, because it is the one unfinished thing in the field that Igneum's existing objects (vote key in the header, coinbase extra data, execution-layer credit by rule, a node per member) make cheap, and nobody has built one on a DAG chain.
|
||||
|
||||
## 6. Group F: mining app UX and the "one click" promise
|
||||
|
||||
### 6.1 The rows
|
||||
|
||||
Minutes to first share are approximate unless a source is named; they count from the download to the first accepted share on a fresh machine with a driver installed.
|
||||
|
||||
| # | Product | Install to first share (min, approximate) | Custody | Fee | What it did that nobody else has | What it left undone | State 2026 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | NiceHash (2014; QuickMiner 2020, approximate) | 5 to 10 | NiceHash wallet, BTC only; 2.5 M users (infobox) | Seller 2 percent, buyer 3 percent (approximate); BTC deposits at or above 0.00005 BTC free, Lightning free above 0.0000001 | A hashpower marketplace: the miner never picks a coin | Custody: 6 Dec 2017 spear-phishing hack, about 4,700 BTC (USD 64 M), Lazarus indicted 17 Feb 2021; repayment completed Dec 2020 (approximate); no node, no wallet of the user's own | Alive |
|
||||
| 2 | HiveOS (2017) | 20 to 40 (flash an image on a second machine or drive, make a web account) | Hiveon pool pays the user's wallet | 2 workers free (3 days of stats); USD 0.50 per card per month to USD 3.00 at 6 or more cards; ASIC USD 2 or free with Hiveon firmware; 10 to 50 percent off at 50 to 1,000 workers | Farm-scale remote management, charts over hours, Telegram and Discord alerts, per-GPU clocks | A second machine or a flash drive; a web account; no local-only mode, no wallet, no node | Alive |
|
||||
| 3 | Minerstat, Awesome Miner | 15 to 30 | User's pool and wallet | minerstat free to 2 rigs, about USD 1.46 per rig per month on larger plans (approximate); Awesome Miner free to 2 miners, paid from about USD 4 per month (approximate) | Awesome Miner: "up to 200,000 ASIC and 25,000 GPU miners" from one Windows console | Management only; no node, no wallet, no pool | Alive |
|
||||
| 4 | Salad (2018) | 5 | Salad Balance, redeemable for PayPal, gift cards, games | Not published | Gaming-PC "earn while idle"; 450,000+ GPUs since 2018; 16,000+ daily active GPUs in 190+ countries today | Mining itself: the site no longer mentions crypto; SaladCloud sells GPU-hours from USD 0.015; the user's take rate is not disclosed; payout in gift cards for years | Pivoted to a compute marketplace |
|
||||
| 5 | Honeyminer (2018) | 3 to 5 | Honeyminer balance, paid in BTC | 8 percent for one GPU, 2.5 percent for two or more (approximate) | The first "download, sign in, earn" miner with no settings | Everything else; the site refuses connections on 7 Oct 2026 | Dead (about 2020 to 2021, approximate) |
|
||||
| 6 | Cudo Miner (2017) | 5 to 10 | Cudo balance | 1.5 to 3 percent by tier (approximate) | Profit switching on Windows, macOS, Linux | The pivot to Cudo Compute (cloud GPU); docs host unresolvable on 7 Oct 2026 | Pivoted |
|
||||
| 7 | Kryptex | 5 ("Download, Launch, Earn"; first payout 1 to 8 hours) | Kryptex balance; withdraw BTC, USDT, ETH, USDC, LTC, BNB, TRX, Volet, Amazon gift cards; 0.00025 BTC minimum | "A small pool fee", unstated | "153,000 GPU and ASIC miners", "10 million+ downloads"; the pool: BTC 4,945 miners at 6.63 EH/s, XMR 37,576 miners at 1.08 GH/s; PPS+ | A balance, not a wallet | Alive |
|
||||
| 8 | unMineable | 5 to 10 | unMineable balance, paid in 80+ assets | 1 percent, 0.75 with a referral code (approximate) | Mine any algorithm, be paid in any coin | A balance; conversion risk; no node | Alive |
|
||||
| 9 | Bitcoin Core "generate" (2009 to 2016) | 0 (one setting) | The user's own wallet, in the node | None | Node, wallet and miner in one process, the only time it has shipped at scale | Removed in 0.13 (2016) once GPUs and chips made it pointless | Dead |
|
||||
| 10 | Monero GUI mining | 0 after sync (Advanced, Mining, Start mining; thread count; local node required) | The user's wallet | None | A wallet with the daemon's CPU miner, and P2Pool bundled and updated per release (nano sidechain 26 Aug 2023, P2Pool 4.18.1 on 6 Oct 2024); the nearest shipped "node plus wallet plus miner" | The GPU (RandomX is CPU work); no tuning, no alerts; the sync wait | Alive |
|
||||
| 11 | Kaspa community miners, the big three | 5 to 15 (a command line, a pool, an address) | The pool's | lolMiner 0.75 percent kHeavyHash, 1.0 Karlsen, 0.7 Ethash, 1.5 Autolykos (v1.96a); BzMiner 1 percent most algorithms, 0.5 ETC, 2 on Pearl and Quantus (v100.45, Windows, Linux, macOS arm64, Docker); GMiner 1 percent kHeavyHash, 2 Ethash and KawPoW, 5 Cortex, "charged continuously"; T-Rex 1 percent (2 on Octopus and Autolykos), 730 open issues, no release date shown; NBMiner 1 to 3 percent, last release 42.3 on 2 Sep 2022 | The per-GPU accepted, rejected, invalid and fault line; kernel speed | No node, no wallet, no tuning against a goal, no signed updates; two of the big three stopped in 2022 (approximate for T-Rex) | lolMiner, BzMiner, GMiner alive; T-Rex and NBMiner stale |
|
||||
|
||||
Nobody in the table ships a miner that is also a verifying node and a wallet with a GPU worker: Bitcoin Core did it for CPUs in 2009 and removed it in 2016; the Monero GUI does it for CPUs with P2Pool bundled; every GPU miner is a kernel plus a pool address, and every "one click" app is a custodial balance.
|
||||
|
||||
### 6.2 Ember against the field
|
||||
|
||||
Ember's state is from `polish.md` 3.1 and 3.10: a Rust engine runs `igneumd` and one GPU worker per card, serves a tokenised local dashboard, Welcome, Cards, Address, the one-time key sheet, Start mining; Ember Tune measured 37 to 41 percent more hashes per watt on PC 1; signed, hash-checked, rolled-back updates with a safe-moment rule; no dev fee in pool mode, a 1-in-100 template solo; pool mode is design only (pool.md section 7).
|
||||
|
||||
| Axis | The field's best | Ember today | Past or behind |
|
||||
|---|---|---|---|
|
||||
| Node, wallet and miner in one process, GPU | Nobody (Monero GUI for CPU) | Yes: `igneumd`, the worker, the key and the address in one app | Past |
|
||||
| Custody | Direct-to-wallet only at P2Pool and HiveOS; every one-click app holds a balance | The coinbase pays the user's own address; the vote key never leaves the machine | Past |
|
||||
| Governance | Nobody | The vote key in every header the card hashes | Past |
|
||||
| Updates | Unsigned zips (the kernel miners); NiceHash and HiveOS auto-update inside an account | Ed25519-signed manifests, sha256, staged, rolled back after 90 unhealthy seconds, a safe-moment rule | Past |
|
||||
| Tuning | HiveOS per-GPU clocks by hand; Nanominer's watchdog | A goal with the money consequence on screen, hill climb, 37 to 41 percent per watt | Past |
|
||||
| Fee | 0.5 to 3 percent dev fees; 1 to 8 percent custodial takes | 1 percent template dev fee solo, 0 in pool mode | Past |
|
||||
| Install to first share | NiceHash, Honeyminer, Kryptex: about 5 minutes with one interstitial | Windows: SmartScreen interstitial, per-user install, a firewall UAC prompt 20 to 50 s in; Mac: an ad hoc signature that macOS 15 refuses without Privacy and Security, Open Anyway (Q3, Q14, Q15); the Windows installer pipeline is dead on GitHub billing (Q80) | Behind: three prompts against one, and no Windows build until Q80 |
|
||||
| Earnings | NiceHash's fiat per day on the main screen | "£0.00 earned" first; mined IGN never shown (Q18) | Behind, 2 hours |
|
||||
| History and alerts | HiveOS hours of charts and Telegram alerts | A 10-minute strip; no outbound alert (Q16) | Behind, 4 hours plus an alert hook |
|
||||
| Per-card faults | lolMiner's status line | "N blocks" per card; `mismatched=` and `faults=` never reach the row (Q13) | Behind, 2 hours |
|
||||
| Remote view | HiveOS, NiceHash Rig Manager | None for the owner (the console is the operator's) | Behind; not ranked here |
|
||||
| Pool mode | Every miner | Design only | Behind; the share sidechain of 5.3 is the finish |
|
||||
| Finality on the surface | Nobody | Nothing during a pause (Q2) | Behind, 3 hours, and nobody else has the concept |
|
||||
|
||||
The honest line: Ember is the first app to ship the thing the table says nobody shipped (node, wallet, GPU miner, own key, signed updates, no custody), and it loses to a 2018 Honeyminer on the sixty seconds between download and first share. The first-run fixes are one Developer ID and one Authenticode certificate (Q3, a the project lead decision) plus the Q80 installer builder; the rest of the behind rows are 11 hours.
|
||||
|
||||
## 7. Verdict table
|
||||
|
||||
| Group | Unfinished item | Who could finish it | Igneum has it | Hours on Igneum | Recommend |
|
||||
|---|---|---|---|---|---|
|
||||
| A | Energy that produces proofs of real state, paid from issuance (Aleo's 2019 promise) | A chain with a prover network and no validator set | Yes once the proof is verified in consensus (`proving_consensus_verify_daa`, 0.3.16, off by default) | 0 beyond the activation decision | Already done; activate |
|
||||
| A | Pay per verified unit of proving at a market price (PoVW) | Boundless, Succinct, Igneum | Yes (pgas metering, 90/10 external jobs) | 0 | Already done |
|
||||
| A | Useful work verified cheaper than done AND sampleable AND wanted | Nobody in 13 years | No, and the bound says why (0.08 percent useful at 100 GH/s) | 0.5 (the ledger row beside F13, new-pow.md rank 8) | Skip |
|
||||
| A | Stored data as the work (Filecoin, Chia) without filler | Lane 8's scheme C | Partly (class v5 candidate, judged) | per new-pow.md rank 1 (16) | Not this lane's call; already ranked |
|
||||
| B | A tweak that lands before the chip (RandomX v2 merged, unscheduled; ProgPoW dead on governance) | A chain whose parameters move without a vote | Yes (era draws every 6 months, families by height, the class ladder) | 0 | Already done |
|
||||
| B | The random item derivation and external cryptanalysis of the mixer | Igneum | No | per asic-history 4.3 additions 2 and 3; the spend (USD 80,000 to 160,000) is a decision owed | Finish, through the existing ranking |
|
||||
| B | The FPGA overlay measurement and the epoch-length reserve | Igneum | Partly (reserve designed on `ca2-epoch`) | per asic-history addition 5 | Finish, through the existing ranking |
|
||||
| B | Tensor-core PoW measured against a chip | Igneum did it (scheme B) | Yes, retired to reserve R8 with the two-output correction | 1 (new-pow.md rank 5) | Already done |
|
||||
| C | A merged-mining BIP that replaces the 2011 spec; any child chain with a say in its parent | Namecoin, RSK | n/a (ruled out) | 0 | Skip |
|
||||
| C | An algo slot or an Igneum-side auxiliary chain | Nobody should | No | 0 | Skip, with the reasons in 3.2 |
|
||||
| D | Finality on PoW without a coin-bought validator set | Zcash Crosslink (not shipped), Kaspa DAGKnight (PRs) | Yes: miner-only 30-day weight, 20 days at 100 percent hashrate for 2/3 | 0 beyond Igneum's own open rows (LEAVE item, 10 percent rule, weight-gated deep fork choice, vote-or-burn: 0.3.16 and horizon 1) | Already done; finish the own rows |
|
||||
| D | A subjective reweighting rule that survives its own chain (Horizen, MESS) | Nobody | Removed on purpose (no hidden-block n^2 penalty) | 0 | Skip |
|
||||
| E | A pool with no operator on a DAG chain | Nobody has; Monero P2Pool is the finished form on a chain | Partly (vote key in the header, the 20 percent paid by sortition, coinbase extra data) | about 40 (section 5.3) | Finish |
|
||||
| E | Miner-built templates (DATUM, SV2 JD: one block in 52,297) | Igneum's mode C | Designed, not built | 6 | Finish after the sidechain |
|
||||
| E | Encrypted member transport | SV2 Noise | No (plain TCP, Q67) | 8 | Finish |
|
||||
| E | A pool-as-contract with sampled share verification (SmartPool) | Nobody since 2017 | No | 0 | Skip (1.35 ms per share makes it worse than it was for Ethereum) |
|
||||
| F | Node, wallet and GPU miner in one app, own key, signed updates | Nobody; Ember | Yes | 0 | Already done |
|
||||
| F | Sixty seconds from download to first share | Honeyminer did it in 2018 | No (three prompts, no Windows build) | 6 plus certificates (Q3); 0 or 4 (Q80) | Finish; the certificates are the project lead's |
|
||||
| F | Earnings in IGN, per-card faults, an hour of history, a finality notice | HiveOS, lolMiner | No (Q18, Q13, Q16, Q2) | 11 | Finish |
|
||||
| F | Payout in something a gamer can spend (Salad's gift cards) | Salad | No, and the chain says "nothing is bought or sold on devnet" | 0 | Skip until there is a market |
|
||||
|
||||
## 8. Three headline findings for the coordinator
|
||||
|
||||
1. Every proof-of-useful-work programme that passed both tests (cheap verify, sampleable) did so by making the work useless, and the one exception, Filecoin, ran at 29 to 36 percent utilisation and lost 55 percent of its raw byte power in the year to 14 Sep 2026; Igneum's split already finishes Aleo's 2019 promise, and the only unfinished line (a majority prover pool earning 11,636 IGN an hour at 51 percent) closes when `proving_consensus_verify_daa` is switched on.
|
||||
|
||||
2. No pool without an operator exists on any chain but Monero (P2Pool at 7.7 percent, 3,520 miners, 0 percent fee; Ocean at 2.05 percent of blocks with 85 percent of them from DATUM; Stratum V2 job declaration at 1 block in 52,297), and Igneum already holds the three objects that make one cheap (the vote key in the header, the 20 percent paid by sortition, the coinbase extra data), so a share sidechain is about 40 agent hours and would be the first on a DAG chain.
|
||||
|
||||
3. Every finality on a PoW chain that held in practice is a second validator set bought with coins (Decred 64 percent of supply staked, eCash 350 B XEC, Dash 1,000 DASH per masternode), and every rule that only reweights work has been switched off by its own chain (MESS twice, Horizen winding down, Kaspa's 12-hour depth subjective); Igneum's work-weighted set needs 20 days of 100 percent hashrate to reach 2/3, so no renter buys it in a day, and the unfinished items are Igneum's own (the LEAVE item and the 10 percent rule), not the industry's.
|
||||
|
||||
## 9. Sources
|
||||
|
||||
All accessed 7 October 2026 unless stated. Lane notes with the full per-item citations sit in the scratchpad under `mission-unfinished/` (A-notes, B-notes, CD-notes, E-notes, F-notes).
|
||||
|
||||
Group A
|
||||
- https://en.wikipedia.org/wiki/Primecoin ; https://www.johndcook.com/blog/2026/01/10/primecoin-primality-test/ ; https://github.com/primecoin/primecoin/wiki/Primecoin-Reaches-13-primes-Milestone ; https://primecoin.io/ ; https://chainz.cryptoid.info/xpm/ ; https://www.coingecko.com/en/coins/primecoin ; https://bitinfocharts.com/primecoin/
|
||||
- https://github.com/gridcoin-community/Gridcoin-Wiki/wiki/zArchive~Proof-of-Research ; https://gridcoin.us/assets/docs/scraper-summary.pdf ; https://gridcoin.us/guides/whitelist.htm ; https://github.com/gridcoin-community/Gridcoin-Research/releases/tag/5.5.0.0
|
||||
- https://curecoin.net/knowledge-base/folding-for-curecoin/how-do-i-start-folding-for-curecoin-quick/ ; https://curecoin.net/news/curecoin-team-reaches-1-trillion-ppd-on-foldinghome/ ; https://curecoin.net/curecoin-roadmap/ ; https://tokenmarket.net/blockchain/counterparty/assets/foldingcoin/
|
||||
- https://eprint.iacr.org/2020/190 ; https://aleo.org/post/aleo-mainnet-faq/ ; https://docs.aleo.org/participate/run-a-node/prover ; https://github.com/ProvableHQ/ARCs/discussions/97 ; https://provable.com/blog/introducing-arc-0046 ; https://aleo.org/post/announcing-snarkos-v4.0.0/ ; https://messari.io/report/state-of-aleo-q2-2025 (search excerpt; direct fetch 429) ; https://followin.io/en/feed/11596817
|
||||
- https://eprint.iacr.org/2021/1379 ; https://eprint.iacr.org/2025/2091 ; https://eprint.iacr.org/2017/203 ; https://eprint.iacr.org/2018/559
|
||||
- https://filecoin.io/blog/posts/a-guide-to-filecoin-storage-mining ; https://lotus.filecoin.io/storage-providers/get-started/hardware-requirements/ ; https://lotus.filecoin.io/storage-providers/operate/benchmarks/ ; https://filecointldr.io/article/key-trends-and-takeaways-from-filecoin-q1-2025 ; https://www.kucoin.com/news/flash/filecoin-q3-2025-report-capacity-drops-10-network-utilization-rises-to-36 ; https://thetradersspread.com/crypto/filecoin-rose-24-percent-ahead-of-75-percent-supply-cut ; https://github.com/filecoin-project/FIPs/discussions/1009 ; https://filecoin.io/blog/posts/introducing-proof-of-data-possession-pdp-verifiable-hot-storage-on-filecoin/
|
||||
- https://www.chia.net/2022/03/19/chia-mainnet-year-one/ ; https://www.chia.net/2021/05/24/chia-and-ssd-endurance/ ; https://www.chia.net/2021/08/03/chia-and-ssd-endurance-big-progress-less-waste/ ; https://www.chia.net/2023/01/21/plotting-chias-future/ ; https://www.chia.net/2026/02/27/changes-coming-to-3-0/ ; https://lowendbox.com/blog/the-crypto-that-ate-all-the-hard-drives-whatever-happened-to-chia/ ; https://xch.today/2026/02/09/top-10-chia-predictions-for-2026/ ; https://en.wikipedia.org/wiki/Chia_Network
|
||||
- https://ownyourmind.ai/projects/flux/ ; https://runonflux.com/ ; https://messari.io/report/state-of-akash-q1-2026-final (search excerpt) ; https://akash.network/blog/akash-network-q1-2026-report/
|
||||
- https://github.com/dynexcoin/Dynex ; https://www.coingecko.com/en/coins/dynex ; https://www.coinlore.com/coin/dynex/news
|
||||
- https://qubic.org/blog-detail/qubic-s-useful-proof-of-work-the-future-of-ai-compute ; https://qubic.org/blog-detail/the-next-evolution-of-useful-proof-of-work-(upow) ; https://www.halborn.com/blog/post/explained-the-monero-51-percent-attack-august-2025 ; https://www.theblock.co/post/366535/monero-faces-chain-reorganization-fears-after-qubic-says-it-controls-51-of-hashrate ; https://cointelegraph.com/news/monero-qubic-selfish-mining-51-percent-attack ; https://www.bitget.com/news/detail/12560604967511 ; https://wublock.substack.com/p/monero-hit-by-a-51-hashrate-attack
|
||||
- https://arxiv.org/html/2507.02951v1 ; https://www.theblock.co/news/web3/2026-03-05-dcg-yuma-bittensor-report-392351 ; https://abittensorjourney.com/p/navigating-bittensor-september-2026 ; https://beincrypto.com/bittensor-subnets-reach-ath-in-may/
|
||||
- https://arxiv.org/abs/2502.19405 ; https://www.gensyn.ai/research/verde-verification-system-in-production ; https://arxiv.org/abs/2405.00295 ; https://blockeden.xyz/blog/2026/04/01/ambient-ai-l1-proof-of-logits-pow-blockchain-decentralized-inference/ ; https://inferencelabs.com/ ; https://arxiv.org/abs/2603.19025 ; https://arxiv.org/pdf/2512.20176 ; https://blockeden.xyz/blog/2026/01/14/boundless-risc-zero-decentralized-proof-market-zk/ ; https://blog.succinct.xyz/succinct-2025-recap/
|
||||
- https://arxiv.org/abs/2209.03865 ; https://eprint.iacr.org/2025/1814 ; https://arxiv.org/abs/2606.06700 ; https://arxiv.org/abs/2606.04819 ; https://arxiv.org/abs/2510.09729
|
||||
|
||||
Group B
|
||||
- https://eips.ethereum.org/EIPS/eip-969 ; https://eips.ethereum.org/EIPS/eip-1057 ; https://ethereum.org/en/developers/docs/consensus-mechanisms/pow/mining-algorithms/ethash/ ; https://github.com/eth-classic/etchash ; https://2miners.com/blog/how-to-mine-ethereum-and-ethereum-classic-on-4gb-gpus/
|
||||
- https://leastauthority.com/static/publications/LeastAuthority-ProgPow-Algorithm-Final-Audit-Report.pdf ; https://github.com/ethcatherders/progpow-audit (PDF text not extractable) ; https://github.com/kik/progpow-exploit ; https://github.com/ethereum/EIPs/issues/2640 ; https://hudsonjameson.com/posts/2020-03-02-progpow-the-ethereum-community-speaks/ ; https://cointelegraph.com/news/the-history-of-the-bitter-debate-over-ethereums-progpow ; https://www.theblock.co/linked/57061/ethereum-community-members-submit-dissenting-progpow-petition ; https://www.coindesk.com/tech/2020/03/05/ethereums-progpow-debate-is-about-much-more-than-mining ; https://www.coindesk.com/tech/2020/03/06/ethereums-progpow-call-features-frustration-but-little-progress ; https://github.com/Souptacular/linzhi ; https://linzhi.io/docs/LWP15-Posts-Against-ProgPoW-05092019.pdf ; https://cryptoslate.com/progpow-authors-steps-down-core-scientific-ethereum/ ; https://ecips.ethereumclassic.org/ECIPs/ecip-1070 ; https://2miners.com/blog/kawpow-new-ravencoin-mining-algorithm/ ; https://en.wikipedia.org/wiki/Firo_(cryptocurrency) ; https://www.qu.ai/blog/quai-network-mainnet-launching-january-29th
|
||||
- https://github.com/tevador/RandomX ; https://github.com/tevador/RandomX/pull/265 ; https://github.com/tevador/RandomX/pull/317 ; https://github.com/monero-project/monero/pull/10038 ; https://github.com/monero-project/research-lab/issues/136 ; https://xmrig.com/docs/algorithms ; https://github.com/xmrig/xmrig-cuda/issues/67 ; https://www.asicminervalue.com/miners/bitmain/antminer-x5 ; https://www.asicminervalue.com/miners/bitmain/antminer-x9 ; https://millionminer.com/news/monero-mining-guide-2026-antminer-x5-x9-pinecone-r1x-randomx (reseller, unverified) ; https://www.coindesk.com/business/2025/08/12/monero-s-51-attack-problem-inside-qubic-s-controversial-network-takeover
|
||||
- https://arxiv.org/abs/1911.05193 ; https://kaspa-lens.com/kaspa-wiki/kaspa-technology-and-features/kheavyhash-proof-of-work-algorithm/ ; https://kaspa-lens.com/kaspa-wiki/getting-started-with-kaspa/mining-kaspa/ ; https://www.asicminervalue.com/miners/iceriver/ks0 ; https://github.com/karlsen-network/karlsend ; https://github.com/spectre-project/rusty-spectre ; https://github.com/Pyrinpyi/pyipad
|
||||
- https://docs.ergoplatform.com/mining/autolykos/ ; https://www.ergoforum.org/t/autolykos-v-2-details/480 ; https://github.com/ergoplatform/eips/blob/master/eip-0027.md ; https://eprint.iacr.org/2020/044 ; https://x.com/RedPandaMining/status/1697408861014196711 ; https://cryptoage.com/en/3019-osprey-e300-fpga-miner-for-kaspa-cryptocurrency.html
|
||||
- https://github.com/mimblewimble/grin/blob/master/doc/pow/pow.md ; https://docs.grin.mw/about-grin/proof-of-work/ ; https://forum.grin.mw/t/grin-v5-0-0-network-upgrade-hard-fork-4-january-2021/7895 ; https://2miners.com/blog/grin-version-4-hard-fork-what-will-change-and-how-to-get-ready/ ; https://forum.grin.mw/t/grn1-cancellation-announcement/5624 ; https://voskcointalk.com/t/new-grin-miner-coming-out/6373 ; https://www.asicminervalue.com/miners/innosilicon/g32-500 ; https://www.asicminervalue.com/miners/ipollo/g1 ; https://2cryptocalc.com/cuckatoo32-algorithm ; https://github.com/tromp/cuckoo
|
||||
- https://coinbureau.com/mining/battle-against-asics-antminer-z9-zcash ; https://github.com/ZcashFoundation/zfnd/blob/master/_posts/blog/2018-05-08-statement-on-asics.md ; https://raw.githubusercontent.com/ZcashFoundation/zfnd/master/_posts/blog/2018-06-07-asic-equihash-study.md ; https://raw.githubusercontent.com/ZcashFoundation/zfnd/master/_posts/blog/2018-07-03-governance-results.md ; https://forum.zcashcommunity.com/t/will-zcash-fork-to-be-asic-resistant/30829 ; https://crypto.news/zcash-doubles-efforts-on-investigating-asic-resistance/ ; https://github.com/BeamMW/beam/wiki/BEAM-Mining ; https://www.minerupdate.com/news/trending-news/beam-hard-fork-executes-to-deter-asic-miners ; https://u.today/privacy-coin-beam-introduces-new-proof-of-work-algorithm ; https://cryptoage.com/en/2095-beam-cryptocurrency-hard-fork-new-beamhash3-mining-algorithm.html ; https://en.wikipedia.org/wiki/Bitcoin_Gold ; https://www.tweaktown.com/news/62048/bitcoin-gold-hit-51-attack-up-18-million-gone/index.html
|
||||
- https://github.com/Conflux-Chain/CIPs/blob/master/CIPs/cip-102.md ; https://forum.conflux.fun/t/cip-102-change-pow-mining-algorithm/16068 ; https://f2pool.io/mining/guides/how-to-mine-conflux/ ; https://www.asicminervalue.com/coins/conflux-cfx
|
||||
- https://docs.alephium.org/frequently-asked-questions/ ; https://docs.alephium.org/mining/ ; https://www.asicminervalue.com/miners/goldshell/al-box ; https://www.asicminervalue.com/miners/bitmain/antminer-al1
|
||||
- https://eprint.iacr.org/2025/685 ; https://ethresear.ch/search.json?q=tensor%20core%20proof%20of%20work
|
||||
- https://arxiv.org/abs/1905.08792 ; https://medium.com/@dave_46855/altered-silicon-x16r-mining-on-the-xilinx-fpga-dbc290c56909 (search excerpt; fetch 403) ; https://forum.zcashcommunity.com/t/equihash-mining-in-fpga/31054 ; https://cryptoage.com/en/3195-all-hashaltcoin-fpgas-get-support-for-the-kheavyhash-algorithm.html
|
||||
|
||||
Group C
|
||||
- https://github.com/namecoin/wiki/blob/master/Merged-Mining.mediawiki ; https://www.namecoin.org/docs/faq/ ; https://en.bitcoin.it/wiki/Merged_mining_specification ; https://metrics.namecoin.org/namecoin/period-timestamps-14-days/pool/charts/latest.txt ; https://forum.namecoin.org/viewtopic.php?f=6&t=2421 (search excerpt; fetch 500) ; https://cryptorank.io/news/feed/7620f-while-bitcoins-hashrate-remains-sky-high-merge-mined-crypto-asset-networks-benefit (search excerpt; fetch 403) ; https://eprint.iacr.org/2017/791
|
||||
- https://www.coindesk.com/markets/2014/08/04/dogecoin-to-allow-litecoin-merge-mining-in-network-security-bid ; https://www.binance.com/en/research/analysis/merged-mining (search excerpt) ; https://www.bitdeer.com/learn/dogecoin-the-logic-of-auxpow-and-stable-profits ; https://www.mexc.com/learn/article/dogecoin-mining-2025-how-doge-is-mined-merge-mining-and-miner-economics/1
|
||||
- https://myriadteam.github.io/ ; https://www.pxdojo.net/2017/04/myriadcoin-untold-story-of-invincible.html ; https://thenextweb.com/news/hackers-verge-blockchain-steal-1-7m ; https://bitcoinist.com/verge-xvg-hacked-35-million-xvg-tokens-reportedly-generated-hacker/ ; https://en.wikipedia.org/wiki/Verge_(cryptocurrency) ; https://cryptonary.com/verge-suffers-reorg-attack-200-days-of-transactions-wiped-away/ (headline only)
|
||||
- https://solofury.com/blog/digibyte-dgb-explained-for-miners/ ; https://www.gemini.com/cryptopedia/digibyte-mining-digibyte-coin-dgb-coin
|
||||
- https://cryptobriefing.com/elastos-bitmain-merged-mining/ ; https://blog.elastos.net/news/ela-queen-of-bitcoin/ ; https://rootstock.io/blog/merged-mining-insights-report-q1-2024/ ; https://rootstock.io/blog/rootstock-x-bitcoin-merged-mining-insights-report-q4-2024/ ; https://rootstock.io/blog/the-security-architecture-of-rootstock-and-the-principle-of-defense/ ; https://github.com/rsksmart/RSKIPs/blob/master/IPs/RSKIP110.md
|
||||
|
||||
Group D
|
||||
- https://docs.decred.org/proof-of-stake/overview/ ; https://docs.decred.org/research/hybrid-design/ ; https://github.com/decred/dcps/blob/master/dcp-0011/dcp-0011.mediawiki ; https://github.com/decred/dcps/blob/master/dcp-0012/dcp-0012.mediawiki ; https://xaur.github.io/decred-news/journal/201912 ; https://xaur.github.io/decred-news/journal/202012 ; https://xaur.github.io/decred-news/journal/202112 ; https://xaur.github.io/decred-news/journal/202212
|
||||
- https://www.coindesk.com/tech/2018/10/10/a-solution-to-cryptos-51-attack-fine-miners-before-it-happens ; https://blog.horizen.io/horizens-51-attack-solution/ (search excerpt; fetch 403) ; https://www.horizen.io/ ; https://docs.horizen.io/
|
||||
- https://raw.githubusercontent.com/kaspanet/rusty-kaspa/master/consensus/core/src/config/constants.rs ; https://raw.githubusercontent.com/kaspanet/rusty-kaspa/master/consensus/core/src/config/bps.rs ; https://raw.githubusercontent.com/kaspanet/rusty-kaspa/master/consensus/core/src/config/params.rs ; https://eprint.iacr.org/2022/1494 ; https://github.com/kaspanet/rusty-kaspa/pull/1104 (search excerpt)
|
||||
- https://arxiv.org/abs/2006.01072 ; https://www.usenix.org/conference/atc20/presentation/li-chenxing ; https://doc.confluxnetwork.org/docs/general/hardforks/v2.0 ; https://doc.confluxnetwork.org/docs/general/conflux-basics/consensus-mechanisms/proof-of-stake/pos_overview
|
||||
- https://arxiv.org/abs/1710.09437 ; https://eips.ethereum.org/EIPS/eip-1011 ; https://eth2book.info/latest/part2/consensus/overview/ ; https://hackmd.io/@tvanepps/Beacon-Book ; https://ethresear.ch/t/convenience-link-to-casper-sharding-chain-v2-1-spec/2332 ; https://github.com/ethereum/pm/blob/master/AllCoreDevs-EL-Meetings/Meeting%2041.md
|
||||
- https://e.cash/blog/ecash-day-2024-celebrating-three-years-of-ecash-xec ; https://e.cash/blog/eCash-day-2026 ; https://e.cash/blog/preconsensus-launch ; https://e.cash/blog/staking-guide ; https://e.cash/blog/avalanche-faq
|
||||
- https://meowsbits.github.io/51-percent-docs/ ; https://ecips.ethereumclassic.org/ECIPs/ecip-1100 ; https://ecips.ethereumclassic.org/ECIPs/ecip-1110 ; https://www.kucoin.com/news/flash/etc-miners-revert-to-argos-after-core-geth-v1-13-0-controversy ; https://github.com/ethereumclassic/core-geth/issues/44 ; https://github.com/ethereumclassic/core-geth/pull/53
|
||||
- https://electric-coin-company.github.io/tfl-book/design/crosslink.html ; https://shieldedlabs.net/crosslink/ ; https://zechub.substack.com/p/arborist-call-88-r-and-d-updates
|
||||
- https://eprint.iacr.org/2020/1101 ; https://www.nervos.org/mining (search excerpt; fetch 429)
|
||||
- https://docs.dash.org/projects/core/en/21.0.0/docs/guide/dash-features-chainlocks.html ; https://syscoincore.org/en/releases/4.2.0/ ; https://komodoplatform.com/en/docs/start-here/core-technology-discussions/delayed-proof-of-work/
|
||||
|
||||
Group E
|
||||
- https://en.bitcoin.it/wiki/P2Pool ; https://github.com/p2pool/p2pool ; https://github.com/p2pool/p2pool/commits/master ; https://github.com/treib-holdings/p2pool.org/issues/1 ; https://github.com/frstrtr/c2pool
|
||||
- https://github.com/SChernykh/p2pool ; https://github.com/SChernykh/p2pool/blob/master/README.md ; https://github.com/SChernykh/p2pool/releases ; https://monero.observer/schernykh-releases-p2pool-v4.7-support-nano-sidechain/ ; https://www.getmonero.org/resources/moneropedia/p2pool.html ; https://data.miningpoolstats.stream/data/monero.js?t=1791359405 ; https://xmrchain.net/api/networkinfo ; https://supportxmr.com/api/pool/stats ; https://api.hashvault.pro/v3/monero/pool/stats ; https://p2pool.observer/ (502 on every try)
|
||||
- https://github.com/TheBlueMatt/bips/blob/betterhash/bip-XXXX.mediawiki ; https://github.com/stratum-mining/sv2-spec ; https://github.com/stratum-mining/stratum/releases ; https://stratumprotocol.org/ ; https://braiins.com/stratum-v2 ; https://braiins.com/pool ; https://d-central.tech/data/stratum-protocol-matrix/ ; https://d-central.tech/bitcoin-mining-pool-comparison-2026/ ; https://www.spark.money/research/bitcoin-stratum-v2-mining-decentralization ; https://cryptobriefing.com/demand-pool-first-stratum-v2-block/ ; https://www.tftc.io/stratum-v2-job-declaration-first-block-dmnd-gomining/ ; https://www.dmnd.work/ ; https://mempool.space/api/v1/mining/pools/1w ; https://mempool.space/api/v1/mining/pools/1y
|
||||
- https://ocean.xyz/docs/tides ; https://ocean.xyz/docs/datum ; https://ocean.xyz/dashboard ; https://bitcoinmagazine.com/technical/an-ocean-launch-post-mortem ; https://crypto.news/bitcoin-mining-divide-pushes-luke-dashjr-out-of-ocean/
|
||||
- https://data.miningpoolstats.stream/data/kaspa.js?t=1791359405 ; https://2miners.com/kas-mining-pool ; https://woolypooly.com/en/coin/kas ; https://f2pool.io/mining/guides/how-to-mine-kaspa/ ; https://kaspa.herominers.com/?lang=en ; https://solopool.org/
|
||||
- https://eprint.iacr.org/2017/019 ; https://blog.acolyer.org/2017/09/07/smartpool-practical-decentralized-mining/ ; https://eprint.iacr.org/2020/044
|
||||
- https://miningpoolstats.net/coins/ravencoin/ ; https://2miners.com/erg-mining-pool
|
||||
- https://github.com/dogestreet/proxypool ; https://arxiv.org/pdf/1112.4980 ; https://arxiv.org/pdf/1402.1718 ; https://arxiv.org/abs/1411.7099 ; https://eprint.iacr.org/2015/155 ; https://en.bitcoin.it/wiki/Eligius
|
||||
|
||||
Group F
|
||||
- https://en.wikipedia.org/wiki/NiceHash ; https://www.nicehash.com/support/general-help/nicehash-service/fees-and-pricing (deposit fees only; the seller and buyer fee pages returned 404)
|
||||
- https://hiveon.com/pricing/
|
||||
- https://www.awesomeminer.com/ ; https://minerstat.com/ (no pricing on either page fetched)
|
||||
- https://salad.com/ ; https://support.salad.com/article/213-how-much-can-i-earn-with-salad (no take rate published)
|
||||
- https://www.kryptex.com/ ; https://pool.kryptex.com/
|
||||
- https://unmineable.com/
|
||||
- https://www.getmonero.org/resources/user-guides/solo_mine_GUI.html ; https://github.com/monero-project/monero-gui/releases
|
||||
- https://github.com/Lolliedieb/lolMiner-releases ; https://github.com/bzminer/bzminer ; https://github.com/develsoftware/GMinerRelease ; https://github.com/trexminer/T-Rex ; https://github.com/NebuTech/NBMiner
|
||||
- Unreachable on 7 Oct 2026: honeyminer.com (connection refused), docs.cudominer.com (no DNS), cudominer.com (429), web.archive.org (blocked for this tool)
|
||||
|
||||
Igneum documents cited
|
||||
- `CLAUDE.md` (the design paragraph, finality rule v2, the fee lines, the 6 October 2026 rules)
|
||||
- `docs/analysis/asic-resistance-history.md` (rows 3, 17 to 25, 30; lessons 5 to 9; sections 4.1 to 4.3)
|
||||
- `docs/plans/pool.md` sections 2, 3, 5, 7; `docs/spec/09-pool-protocol.md` sections 9.1 to 9.8
|
||||
- Scratch inputs: `mission-inputs/horizon/new-pow.md` sections 3.1, 3.3, 6, 7; `mission-inputs/horizon/frontier.md` sections 0, 3.10, 3.11; `mission-inputs/horizon-2026-10.md` section 1; `mission-inputs/horizon/polish.md` sections 3.1, 3.6, 3.10
|
||||
75
docs/analysis/prover-tiers-real-cards.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Prover tiers on real cards: the memory matrix of the patched SP1 GPU server, measured on rented GPUs
|
||||
|
||||
6 October 2026, from 11:50 UTC (the project lead: "rent all you need, absolute overkill", "get as many GPUs as you need to properly
|
||||
test everything swiftly"). Branch `gpu-fleet`, tools in `tools/fleet/`, raw logs per instance under
|
||||
`~/Desktop/fleet/<instance>/` (the sampler csv, every point's host log and results JSON, the miner log). This file
|
||||
replaces the 5090-allocation rows of `docs/analysis/prover-floor.md` with the cards themselves. A row that is not here
|
||||
yet is still running; the table is rewritten by `tools/fleet/collect.py` as rows land.
|
||||
|
||||
## Method
|
||||
|
||||
Every box is a Vast.ai container (nvidia/cuda:12.8.1-devel-ubuntu24.04, the host's own driver) with one card. On it:
|
||||
the 0.3.12 Linux node 83089544 on the ten-field override (digest 7bd98cc4...), peered with the seed; the 0.3.12 CUDA
|
||||
worker; the patched `sp1-gpu-server` built on the box from SP1 v6.8.1 (c84ada1e) with
|
||||
`proving/prover-floor/sp1-gpu-6.8.1-floor.patch` v4 (sha256 e81cb0d0...) for the card's own arch (86 for Ampere, 89
|
||||
for Ada, 120 for Blackwell; the 4090 box built 86,89,120); the cuda host and exporter from the prover-floor bundle
|
||||
(pinned ids 0x2b1a81cb... and 0x474678f3..., `--mode id` on every box). The fixture is the v1 shard
|
||||
(`proving/fixtures/fees-v1-shards2.json` shard 0, 4,717,439 cycles), the same as `prover-floor.md`.
|
||||
|
||||
| Row | What runs | How it is read |
|
||||
|---|---|---|
|
||||
| idle | nothing on the card | `nvidia-smi memory.used`, `power.draw` |
|
||||
| miner | `igneum-miner mine` with the CUDA worker against the box's node, 60 s warm, 150 s sampled | the mean of the STATUS line's `now=` over the sample; watts and memory from a 1-s sampler |
|
||||
| stock | the SDK's own `sp1_gpu_server_v6.8.1` (HOME=/root), `--mode compressed --shard 0` | the host's RESULT line, or the refusal in its log |
|
||||
| alone | the patched server, `SP1_GPU_ELEMENT_THRESHOLD` 2^26 and 2^27 compressed, 2^25 and 2^26 `--mode core`, nothing else on the card | peak = max `memory.used` at 1 s; own = peak minus the reading before the point |
|
||||
| beside | the same points with the miner running on the card (45 s warm before the first) | the same; base = the miner's resident set |
|
||||
|
||||
Every proof is verified by the host's own SDK verifier (the VERIFIED word in the RESULT line; the patch changes buffer
|
||||
sizes and the shard split, not the circuit). The server is killed and its socket unlinked around every point. A point
|
||||
whose allocation does not fit does not fail: the patched server hangs at the card's limit at 0% utilisation (the 3080
|
||||
and the 4060 Ti 8 GB at 2^27, 15 minutes each until killed), so every prover run needs a wall-clock timeout.
|
||||
|
||||
## The table (rewritten as rows land)
|
||||
|
||||
| Card | VRAM GB | Idle MiB | Miner | Stock SP1 6.8.1 | Patched, proves alone (own) | Beside the miner (peak) | Core-only beside the miner (own) | Verdict |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| RTX 3060 | 12 | 1 | 23.78 MH/s at 103.7 W, 1.4 GB | refused: thread 'tokio-rt-worker' (48952) panicked at sp1-gpu/crates/ | 7.4 GB, 14.4 s (alone-comp-26-v1) | 8.9 GB peak, 37.5 s | 5.6 GB, 27.2 s | mines and proves |
|
||||
| RTX 3080 | 10 | 11 | 40.82 MH/s at 204.9 W, 1.5 GB | refused: thread 'tokio-rt-worker' (49593) panicked at sp1-gpu/crates/ | 8.0 GB, 7.1 s (alone-comp-26-v1) | 9.2 GB peak, 25.6 s | 5.9 GB, 19.2 s | mines and proves |
|
||||
| RTX 3090 | 24 | 1 | 37.79 MH/s at 228.8 W, 1.5 GB | not measured: the SDK's server download stalled (killed at 553 s) | 7.7 GB, 14.9 s (alone-comp-26-v1) | 9.3 GB peak, 19.9 s | 5.8 GB, 13.3 s | mines and proves |
|
||||
| RTX 4060 Ti 16 GB | 16 | 0 | 17.58 MH/s at 72.3 W, 1.4 GB | refused: thread 'tokio-rt-worker' (49293) panicked at sp1-gpu/crates/ | 7.8 GB, 11.6 s (alone-comp-26-v1) | 9.0 GB peak, 34.6 s | 5.8 GB, 25.9 s | mines and proves |
|
||||
| RTX 4060 Ti 8 GB | 8 | 0 | 19.07 MH/s at 72.6 W, 1.4 GB | refused: thread 'tokio-rt-worker' (43763) panicked at sp1-gpu/crates/ | 7.6 GB, 9.6 s (alone-comp-26-v1) | no GB peak, s | 5.8 GB, 26.3 s | mines and proves core-only |
|
||||
| RTX 4060 | 8 | 2 | 17.07 MH/s at 0.0 W, 1.4 GB | refused: thread 'tokio-rt-worker' (48510) panicked at sp1-gpu/crates/ | 7.4 GB, 18.4 s (alone-comp-26-v1) | no GB peak, s | 5.6 GB, 22.1 s | mines and proves core-only |
|
||||
| RTX 4070 | 12 | 9 | 24.99 MH/s at 91.1 W, 1.4 GB | refused: thread 'tokio-rt-worker' (47475) panicked at sp1-gpu/crates/ | 7.6 GB, 12.1 s (alone-comp-26-v1) | 10.1 GB peak, 27.3 s | 5.6 GB, 14.3 s | mines and proves |
|
||||
| RTX 4090 | 24 | 1 | 52.25 MH/s at 183.1 W, 1.7 GB | proved 5.6 s at 17.4 GB | 7.9 GB, 6.3 s (alone-comp-26-v1) | 10.7 GB peak, 26.1 s | 6.1 GB, 10.6 s | mines and proves |
|
||||
| RTX 5070 | 12 | 2 | 41.89 MH/s at 137.0 W, 2.7 GB | refused: thread 'tokio-rt-worker' (53680) panicked at sp1-gpu/crates/ | 7.6 GB, 4.8 s (alone-comp-26-v1) | 10.2 GB peak, 37.2 s | 5.8 GB, 19.8 s | mines and proves |
|
||||
| RTX 5090 | 32 | 2 | 98.48 MH/s at 258.2 W, 1.8 GB | proved 8.4 s at 18.3 GB | 8.0 GB, 6.3 s (alone-comp-26-v1) | 9.9 GB peak, 10.7 s | 6.3 GB, 7.4 s | mines and proves |
|
||||
| RTX A5000 | 24 | 1 | 47.7 MH/s at 222.7 W, 1.5 GB | proved 6.4 s at 17.2 GB | 7.7 GB, 8.3 s (alone-comp-26-v1) | 10.5 GB peak, 34.6 s | 6.0 GB, 18.2 s | mines and proves |
|
||||
|
||||
## What the rows say, per tier
|
||||
|
||||
Measured, all eleven cards complete at 13:27Z (4090, 3090, A5000, 5090, 4070, 5070, 3060, 3080, 4060 Ti 16 GB, 4060, 4060 Ti 8 GB):
|
||||
|
||||
| Tier | What the cards say | Consequence | What is being done |
|
||||
|---|---|---|---|
|
||||
| 24 GB (4090, 3090, A5000) | the STOCK server proves the v1 shard (5.6 s at 17.4 GB on the 4090, 6.4 s at 17.2 GB on the A5000; on the 3090 the SDK's server download stalled and the point was killed, not a refusal); the patched 2^26 profile does it in 7.7 to 7.9 GB (6.3 s 4090, 8.3 s A5000, 14.9 s 3090) and beside the miner the peak is 9.3 GB (3090, 19.9 s) to 10.5 to 10.7 GB (A5000, 4090; 26 to 35 s); the 3090 mines 37.8 MH/s at 229 W, the weakest MH/W of the 24 GB cards | mines and proves with 13 GB to spare; the patched profile frees 9.5 GB for nothing but a 1.1 to 1.3x slower proof, so a 24 GB card keeps upstream's threshold and the public line "24 GB: mines and proves" stands on real cards | the 24 GB rows go into the fleet night as compressed provers at the default tier |
|
||||
| 12 GB (3060, 4070, 5070) | the stock server refuses (the 24 GB gate); patched 2^26 proves alone at 7.4 to 7.6 GB (14.4 s on the 3060, 12.1 s on the 4070, 4.8 s on the 5070); BESIDE THE MINER the peak is 8.9 GB (3060, 37.5 s) to 10.1 to 10.2 GB (4070, 5070; 27.3 s and 37.2 s) of 12 GB, verified; core-only beside the miner 5.6 to 5.8 GB (14.3 to 27.2 s) | a 12 GB card mines and proves compressed shards on the patched server with about 2 GB to spare before the display (Windows and a monitor take 0.5 to 1.5 GB, so a desktop 12 GB card is at the edge; a headless Linux one is fine); the 9.0 GB line of prover-floor.md is not needed for Linux headless, and core-only (5.6 to 5.8 GB) keeps 6 GB spare for a desktop | the public line becomes "12 GB: mines and proves on Linux (the patched server), proves alone on a desktop; core-only mine-and-prove on a desktop once the hand-off ships"; the 4070 and 5070 join the fleet night with the miner PAUSED per segment (prove-alone profile), the 10.2 GB beside-row is the mine-and-prove candidate for a second night |
|
||||
| 16 GB (4060 Ti 16 GB) | patched 2^26 alone 7.8 GB in 11.6 s; beside the miner 9.0 GB peak of 16 GB in 34.6 s; core-only beside 5.8 GB | mines and proves with 7 GB spare, display or not; the 2^27 profile (10 GB alone) fits too | joins the fleet night mining and proving compressed |
|
||||
| 10 GB (3080) | proves alone at 2^26 (7.1 s, 8,158 MiB own); BESIDE THE MINER compressed 2^26 verified at a 9,412 MiB peak of 10,240 in 25.6 s; core-only beside 2^25 at a 7,618 MiB peak in 19.2 s; 2^27 panics after 568 s | mines and proves on headless Linux with 0.8 GB spare, too close for a desktop with a display, where core-only (2.6 GB spare) is the profile | the app keeps 8 and 10 GB cards "prove alone, off by default while mining" (the prover-floor agent's provedefault rows from these numbers) |
|
||||
| 8 GB (4060, 4060 Ti 8 GB) | both prove alone at 2^26 (the 4060 Ti 9.6 s at 7,740 MiB of 8,188; the 4060 16.9 s at 7,655), at 2^25 compressed (13.5 s, 7,676) and core-only at 2^25 (5.8 s, 5,916) and 2^24 (11.2 s, 5,404); BESIDE THE MINER (1.4 GB resident) compressed 2^26 does not fit on either, core-only 2^25 proves verified at a 7,123 MiB (4060, 22.1 s) and 7,352 MiB (4060 Ti, 26.3 s) peak of 8,188, core 2^24 at 6,840 to 6,867 (60 to 70 s) | an 8 GB card proves alone compressed, or mines and proves core-only at 2^25 with about 1 GB spare on headless Linux (a desktop with a display takes 2^24, 1.3 GB spare, at 60 to 70 s a shard); the 8 GB miner's hash while proving falls to 16.0 to 18.4 MH/s from 17.1 to 19.1 | the public floor becomes "8 GB proves alone; mines and proves core-only once the hand-off ships" |
|
||||
| 32 GB (5090) | 98.5 MH/s at 258 W; the stock server proves in 8.4 s at 18.3 GB; patched 2^26 alone 8.0 GB in 6.3 s; beside the miner 9.9 GB peak in 10.7 s (the miner costs 1.7x here against 4x on Ada) | the strongest prover per card: beside its miner it proves a v1 shard every 11 s | the fleet night's compressed prover at upstream's tier |
|
||||
| rig | one server per card at the card's profile: a 4090 rig needs 8 x 10.7 GB device memory beside its miners and about 6 GB of host RAM per server | fits any 8x 4090 rig with 64 GB of host RAM | phase 3 measures it |
|
||||
| pool user | nothing changes: the pool's provers carry the proofs | | |
|
||||
|
||||
The empty-shard rows (the `block-72854-empty-block-first` fixture) prove SLOWER than the v1 shard on every card (24 to 50 s at 2^26 against 6 to 12 s), the opposite of PC 2's 3.3 s for its own empty shard; the fixture is a first block with a genesis-state witness, not an empty live shard, so those rows are not the "empty block" cost and are left out of the tier line.
|
||||
|
||||
## Ember Tune on rented cards
|
||||
|
||||
`nvidia-smi -pl` and `-lgc` are refused inside a Vast container (the host's driver holds the power and clock knobs), so
|
||||
the two-knob ladder (`tools/fleet/box-ember.sh`) reports one baseline step per card: the untuned MH/s, W and MH/W. The
|
||||
rows are in `results.json` (`tune_plan: baseline`) and in the fleet priors section of `docs/plans/ember-tune.md`; a
|
||||
tuned point per model needs a bare-metal host or a VM with the driver inside.
|
||||
|
||||
| Card | MH/s | W | MH/W | Plan |
|
||||
|---|---|---|---|---|
|
||||
| RTX 4070 | 24.77 | 91.3 | 0.2713 | baseline |
|
||||
| RTX 4090 | 52.24 | 179.9 | 0.2904 | baseline |
|
||||
|
|
@ -1669,6 +1669,18 @@ Branch `ember-tune` (54ff1bc), docs/plans/ember-tune.md. Every card tuned for MH
|
|||
|
||||
Nothing set; the installed app's miners back after 350 s. Why every earlier run (5 and 6 October, runs 1 to 4 and dry runs 1 and 2) read its copied settings as defaults, measured on PC 1 (collect ember-acl-2): the engine's own start locks its app folder with `icacls /inheritance:r /grant:r <user>:F`; cutting the folder's inheritance propagates down, the non-inheritable grant gives the children nothing, so a file COPIED in before the start (settings.json, machine-id, wallet.json) is left with no access entry and its owner cannot read it (`ReadAllText`: access denied), while the engine's own files written after the lock inherit fine, which hid it for a day. A first fix with `(OI)(CI)F /T` left the file empty too: `/T` re-applies `/inheritance:r` to each file after the propagation and an `(OI)(CI)` entry on a file is inherit-only. The right form is the inheritable grant without `/T` (07d5a72). Consequence for every tier on Windows: nothing changes for the installed app (its files were always its own); any tool that drops files into the app folder before the app starts (an installer's seed, a migration, a support script) was unreadable to the app until now and is readable from 0.3.13 on.
|
||||
|
||||
**Run 5, 6 October 2026, 15:28 to 15:39Z (job ember-tune-pc1-5, elevated on the project lead's click, PC 1 on 0.3.13, the tune engine = kit ember-kit-5 from 07d5a72, mode=direct):** the first run that set limits. The 5090's power ladder, 75 s a step, the clock unlocked (2,850 MHz core, 13,801 MHz memory), the rate = the worker's STATUS wall rate, the draw = nvidia-smi every 5 s:
|
||||
|
||||
| Cap | Limit | MH/s | W | MH/W | GPU C |
|
||||
|---|---|---|---|---|---|
|
||||
| 100% | 575 W | 127.38 | 309.9 | 0.411 | 64 |
|
||||
| 90% | 518 W | 99.32 | 313.6 | 0.317 | 65 |
|
||||
| 80% | 460 W | 123.11 | 312.2 | 0.394 | 65 |
|
||||
| 70% | 403 W | 127.38 | 310.9 | 0.410 | 65 |
|
||||
| 60% (floor 400 W) | 400 W | 127.38 | 311.3 | 0.409 | 65 |
|
||||
|
||||
Reading: the cap does not bind on this hash (310 to 314 W under every limit, as the 4 October stability line said), so the power knob is flat at 0.41 MH/W on the 5090 and the saving must come from the clocks; the 90% and 80% rows' rate dips at the same draw are stalls inside those holds (a worker restart or a template wait), not the cap. The clock ladder's first step (2,781 MHz at 575 W) was requested at 15:37:47Z and never measured: the playbook's own watchdog killed the live engine at 15:39:03Z (all three cards mining at 177 MH/s) because its idle clause sampled one log line and read "idle" from a missing match; the 4070's and the 9070 XT's plans never ran. Consequences: the 5090 is probably left with its core clock locked at 2,781 MHz (an `-lgc` lock persists until `-rgc` or a reboot) under the 575 W cap, which costs little rate; freeing it needs administrator rights; and the Power Helper was NOT registered by this run (the registration lived only in the installed app's cap path, which a `--sweep` engine skips). Fixed the same hour: the watchdog's idle rule (three consecutive status lines reading 0.00 MH/s and 300 s, never a missing match), the `after` snapshot and the engine-log dump on every exit, and an elevated tune engine registering the task itself before its first step. The decided way out: the project lead switches Power control ON in the 0.3.13 app (its one prompt registers the task from the install folder), a job frees the clock through the task (`rgc`), the tune runs unelevated through the task.
|
||||
|
||||
Consequence for the tiers: an AMD card is tuned on its power limit alone until its stock core clock is read (a 9070 XT at -30% is the floor the driver allows, 4 steps, 5 minutes); every NVIDIA card's two-knob plan waits on the user's one click on Power control; the re-run on PC 1 is held until the quit's source is named (the event-log collect) and follows the 0.3.11 rollout (the update clears the jobs folder, so the engine and the helper are fetched again), with the scheduler's slot.
|
||||
## 5 October 2026 (night), read width of the lottery hash: 4, 16 and 64-byte loads, a per-load mix, a written scratch; three cards (gate 1 experiment, cryptographer)
|
||||
|
||||
|
|
@ -2527,3 +2539,116 @@ A fleet of real GPU boxes ran a fleet-only chain from the devnet's genesis state
|
|||
| The stall | 90 s at the flip and then a stop: the shipped worker of the previous release refuses a generator-4 pack by design; the fleet resumed on the release tree's generator-4 worker 15 minutes later |
|
||||
|
||||
What it means per tier: a class change on this chain is decided by the miners' own signal and lands at an epoch boundary with no human release; an old node cannot join the new chain and an old worker cannot mine the new class, so a home miner, a rig and a pool update the node and the worker together before the signal window closes, which the one-click app does in one update; a chip built for the old class mines nothing from the flip. The live cut carries the one lesson: the new object is published only once every node and every worker runs the release that reads it.
|
||||
|
||||
## 6 October 2026, 16:01Z: Ember run 6 on PC 1 (ember-tune-pc1-6, 0.3.13 + kit-6 = 564bdea, elevated, one click)
|
||||
|
||||
The helper registered inside the run but on the scratch copy (fixed: task_exe, the `reregister` verb; see the plan's
|
||||
run 6 notes). RTX 5090: chosen 1854 MHz at 100% = 127.71 MH/s at 226.8 W, 0.563 MH/W, against 127.9 at 311.0 W
|
||||
(0.411) untuned: 84 W saved for 0.15% of rate. Every cap step 60 to 100% read 311 to 313 W (the cap never binds).
|
||||
The clock ladder: 2781 MHz 298.8 W 0.428; 2472 MHz 262.0 W 0.488; 2163 MHz 239.9 W 0.533; 1854 MHz 226.8 W 0.563
|
||||
(the floor, not the optimum: the next cut's ladder goes to 45%). RTX 4070: caps 100 to 60% all 106.0 W 28.71 MH/s
|
||||
(0.271); 50% 99.1 W 28.70 (0.290); clock 2794 MHz at 50% 99.1 W (0.290); 2484 MHz 81.1 W 28.73 (0.354); further rows
|
||||
and the 9070 XT ladder below once the run closes.
|
||||
|
||||
Run 6 closed 16:39:37Z, exit 0, 2317 s, 19 rows. RTX 4070 chosen 1863 MHz at 50% = 28.78 MH/s at 75.6 W (0.381) against
|
||||
28.72 at 106.0 W (0.271): 30 W saved for no rate lost; its clock ladder at 50%: 2794 MHz 99.1 W 0.290; 2484 MHz 81.1 W
|
||||
0.354; 2173 MHz 77.7 W 0.370; 1863 MHz 75.6 W 0.381 (the floor). RX 9070 XT: aborted at step 1, "card reports 0 W,
|
||||
acknowledged true" = the applied rule demanded watts from a card that reports offsets (fixed bd7fcf4); the draw itself
|
||||
was read on every tick (amd_watts_source=engine_telemetry, 363 samples). The helper registered on the scratch copy
|
||||
(fixed 200362a: task_exe, the reregister verb). PC 1 mined through the installed app again by 16:44Z: 170.6 MH/s over
|
||||
the three cards, 0 faults.
|
||||
|
||||
Re-point and proof, 16:48 to 16:53Z: the Power Helper task re-pointed by the helper itself ("1 reregister ok: the
|
||||
task now runs ...Programs\Igneum Miner\igneum-app.exe --power-helper"), then the installed app's caps through the
|
||||
task with no prompt ("-pl 460: set to 460.00 W from 575.00 W", "-pl 160: set to 160.00 W from 100.00 W"). Task
|
||||
Running, Highest, user Admin. Cards: 5090 221 W at 1845 MHz, 4070 75.8 W at 1860 MHz, 9070 XT 202 W, all mining
|
||||
through the installed app. Ember closed 16:53Z.
|
||||
|
||||
## Prover tiers on real cards: the rented fleet, 6 October 2026 (branch gpu-fleet)
|
||||
|
||||
From 11:50 UTC, Vast.ai containers (nvidia/cuda:12.8.1-devel-ubuntu24.04, the host's driver), one card each, the
|
||||
0.3.12 Linux node 83089544 on the ten-field override, the 0.3.12 CUDA worker, the patched SP1 server built on each box
|
||||
from `proving/prover-floor/sp1-gpu-6.8.1-floor.patch` v4 (e81cb0d0...) for the card's own arch, the cuda host with the
|
||||
pinned ids 0x2b1a81cb... and 0x474678f3...; the v1 shard (`fees-v1-shards2.json` shard 0, 4,717,439 cycles); every
|
||||
proof VERIFIED by the host's own SDK verifier; peak = nvidia-smi memory.used sampled once a second (a per-second loop,
|
||||
not `-l 1`, which buffers and ignores SIGTERM in a container); own = peak minus the reading before the point; beside
|
||||
= the card's miner running (its resident set is the base). Runner `tools/fleet/box-matrix.sh`, collector
|
||||
`tools/fleet/collect.py`, raw logs `~/Desktop/fleet/<instance>/`, the analysis `docs/analysis/prover-tiers-real-cards.md`.
|
||||
|
||||
| Card | VRAM GB | Idle MiB | Miner | Stock SP1 6.8.1 | Patched, proves alone (own) | Beside the miner (peak) | Core-only beside the miner (own) | Verdict |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| RTX 3060 | 12 | 1 | 23.78 MH/s at 103.7 W, 1.4 GB | refused: thread 'tokio-rt-worker' (48952) panicked at sp1-gpu/crates/ | 7.4 GB, 14.4 s (alone-comp-26-v1) | 8.9 GB peak, 37.5 s | 5.6 GB, 27.2 s | mines and proves |
|
||||
| RTX 3080 | 10 | 11 | 40.82 MH/s at 204.9 W, 1.5 GB | refused: thread 'tokio-rt-worker' (49593) panicked at sp1-gpu/crates/ | 8.0 GB, 7.1 s (alone-comp-26-v1) | 9.2 GB peak, 25.6 s | 5.9 GB, 19.2 s | mines and proves |
|
||||
| RTX 3090 | 24 | 1 | 37.79 MH/s at 228.8 W, 1.5 GB | not measured: the SDK's server download stalled (killed at 553 s) | 7.7 GB, 14.9 s (alone-comp-26-v1) | 9.3 GB peak, 19.9 s | 5.8 GB, 13.3 s | mines and proves |
|
||||
| RTX 4060 Ti 16 GB | 16 | 0 | 17.58 MH/s at 72.3 W, 1.4 GB | refused: thread 'tokio-rt-worker' (49293) panicked at sp1-gpu/crates/ | 7.8 GB, 11.6 s (alone-comp-26-v1) | 9.0 GB peak, 34.6 s | 5.8 GB, 25.9 s | mines and proves |
|
||||
| RTX 4060 Ti 8 GB | 8 | 0 | 19.07 MH/s at 72.6 W, 1.4 GB | refused: thread 'tokio-rt-worker' (43763) panicked at sp1-gpu/crates/ | 7.6 GB, 9.6 s (alone-comp-26-v1) | no GB peak, s | 5.8 GB, 26.3 s | mines and proves core-only |
|
||||
| RTX 4060 | 8 | 2 | 17.07 MH/s at 0.0 W, 1.4 GB | refused: thread 'tokio-rt-worker' (48510) panicked at sp1-gpu/crates/ | 7.4 GB, 18.4 s (alone-comp-26-v1) | no GB peak, s | 5.6 GB, 22.1 s | mines and proves core-only |
|
||||
| RTX 4070 | 12 | 9 | 24.99 MH/s at 91.1 W, 1.4 GB | refused: thread 'tokio-rt-worker' (47475) panicked at sp1-gpu/crates/ | 7.6 GB, 12.1 s (alone-comp-26-v1) | 10.1 GB peak, 27.3 s | 5.6 GB, 14.3 s | mines and proves |
|
||||
| RTX 4090 | 24 | 1 | 52.25 MH/s at 183.1 W, 1.7 GB | proved 5.6 s at 17.4 GB | 7.9 GB, 6.3 s (alone-comp-26-v1) | 10.7 GB peak, 26.1 s | 6.1 GB, 10.6 s | mines and proves |
|
||||
| RTX 5070 | 12 | 2 | 41.89 MH/s at 137.0 W, 2.7 GB | refused: thread 'tokio-rt-worker' (53680) panicked at sp1-gpu/crates/ | 7.6 GB, 4.8 s (alone-comp-26-v1) | 10.2 GB peak, 37.2 s | 5.8 GB, 19.8 s | mines and proves |
|
||||
| RTX 5090 | 32 | 2 | 98.48 MH/s at 258.2 W, 1.8 GB | proved 8.4 s at 18.3 GB | 8.0 GB, 6.3 s (alone-comp-26-v1) | 9.9 GB peak, 10.7 s | 6.3 GB, 7.4 s | mines and proves |
|
||||
| RTX A5000 | 24 | 1 | 47.7 MH/s at 222.7 W, 1.5 GB | proved 6.4 s at 17.2 GB | 7.7 GB, 8.3 s (alone-comp-26-v1) | 10.5 GB peak, 34.6 s | 6.0 GB, 18.2 s | mines and proves |
|
||||
|
||||
Also measured: the stock SP1 6.8.1 server refuses every card under 24 GB at `builder.rs:38` and proves the v1 shard on
|
||||
the 4090 (5.6 s, 17.4 GB), the A5000 (6.4 s, 17.2 GB) and the 5090 (8.4 s, 18.3 GB); 2^27 does not fit a 10 or 8 GB card
|
||||
and the v4 server hangs at the card's limit (568 and 904 s until killed) where v5 aborts in 13 s ("FLOOR abort: a device
|
||||
allocation failed at slop/crates/tensor/src/inner.rs:51 ... AllocError { size: 486586112 }", exit 70; the known-failed
|
||||
case of the prover-floor gate, on the 3080); the miner beside a prover costs 1.7x (5090) to 7.7x (5070) on the proof's
|
||||
time and 5 to 20% of the miner's rate; the empty-shard fixture (block-72854, a first block with a genesis witness) proves
|
||||
slower than the v1 shard on every card (24 to 55 s) and is not an empty live shard; Ember's two knobs are refused in the
|
||||
containers, so the ladders are baseline rows (`docs/plans/ember-tune.md`, fleet priors).
|
||||
|
||||
## Rental cost of hash, 6 October 2026 (branch gpu-fleet): what a GH/s costs by the hour against the devnet
|
||||
|
||||
Measured on the rented fleet (RunPod community pods, list prices, 18:45Z): 38 wave pods (4090, A4000, L4, 3090, 3070, 4070 Ti,
|
||||
A5000, 4000 Ada) ran 1,748 MH/s inside jobs (median pod 28 MH/s) for USD 20.44 an hour, USD 0.0117 per MH/s-hour; the 8x 4090 rig
|
||||
459 MH/s at 1,636 W for USD 5.92 an hour, USD 0.0129 per MH/s-hour; a single 5090 pod 98 to 128 MH/s for USD 0.41 to 0.74 an hour.
|
||||
The live devnet's difficulty read 1,156,040,186 at 1 block a second at 19:00Z, so the whole network was about 1.16 GH/s, and the
|
||||
rented fleet was most of it. The live litepaper's line ("2 GH/s for USD 13/h vs 280 MH/s devnet") is corrected to: 1.75 GH/s for
|
||||
USD 20 an hour on community pods, against a devnet of 1.16 GH/s.
|
||||
|
||||
| Buyer | What USD 20/h buys | Against the devnet (1.16 GH/s) | Against mainnet scale |
|
||||
|---|---|---|---|
|
||||
| Home miner, 8 to 12 GB card (11 to 28 MH/s) | nothing: the card is owned, 0.15 to 0.2 kW | one card is 1 to 2 percent of the devnet | one card is noise at a TH/s |
|
||||
| Rig, 8x 4090 (459 MH/s, USD 5.92/h rented) | 1.7 rigs | one rig is 40 percent of the devnet | one rig is 0.05 percent of a TH/s |
|
||||
| Renter at RunPod list prices | 1.75 GH/s while cards exist | 150 percent of the devnet: overtaken for USD 15/h | a TH/s costs USD 11,700 an hour and the market cannot supply it: asked for 20 pods of any of 8 card types at 18:59Z to 19:15Z, RunPod gave 0 ("no instances currently available") |
|
||||
|
||||
Consequence: the devnet's hash is rentable for the price of a dinner, so nothing on it is a security result; the counter-ASIC and
|
||||
finality work is tested there for correctness, not for cost. The cost argument only starts at the TH/s scale, where the rental
|
||||
market's supply (not its price) is the limit, and that number belongs in the litepaper with this caveat.
|
||||
|
||||
## Block rate on Devnet 2, 6 October 2026 (branch gpu-fleet): 10 blocks per second against 1 on 42 rented cards
|
||||
|
||||
Run A (10 blocks/s profile, star topology, 65 min): 4.87 DAG blocks/s, 1.09 blue blocks/s, 77.6 percent red, tips 250 to 660,
|
||||
max reorg 55, difficulty easing all hour (6,719 to 1,307), the exec follower at 0.05 blocks/s (lag 18,901 at the end). Run B
|
||||
(1 block/s, same boxes, 30 min): 1.41 blocks/s over the window with the join burst, 1.0 blocks/s and under 2 percent red from
|
||||
minute six, tips 1 to 3, difficulty settled in six minutes (453 to 482 M), the exec follower at 0.46 blocks/s. The network
|
||||
lane's read: run A's reds came from node throughput (61 to 345 ms CPU per accepted block at mergeset 8 to 200), not from the
|
||||
star. Per tier the blue rate decides the payout interval (1.09 against 1.19 blue/s: a 4070 at 10 TH/s waits about three days
|
||||
for a paying block either way), so the higher rate buys the solo miner nothing until the node processes a block in under 50 ms
|
||||
at mergeset 248. Recommendation (the lane's): 1 block/s for the testnet and the launch, 10 behind three measured gates.
|
||||
Full tables and sources: `docs/analysis/block-rate-devnet2.md`, rows in `~/Desktop/fleet/bps/{A,B}.jsonl`.
|
||||
|
||||
## The rented fleet is the devnet's finality, 6 October 2026 (branch gpu-fleet)
|
||||
|
||||
Measured at 21:57Z from the hub's last 2,000 blocks: the 38 wave pods held 77.8 percent of the voter weight (mean 2.05 percent
|
||||
a pod), the 14 standing boxes most of the rest, the hands and the hub the remainder; the last lock signed 93.5 percent of the
|
||||
active voters and 89.9 percent of the frozen table (53 of 84 voters on the first certificate). Earlier the same evening the
|
||||
fleet removed 13 miners' GPUs inside three minutes and finality paused for two hours five minutes (18:39:36Z to 20:44:44Z,
|
||||
42.7 percent of the table gone with earlier leavers; rule v3 holds a full window). From that came the 10 percent rule (never
|
||||
remove more than 10 percent of the live devnet's weight in an hour, `tools/fleet/lib/standing.py weight_check`) and the wave's
|
||||
wind-down by hourly slices (`tools/fleet/winddown.py`: slice 1 at 21:58Z took 12 pods and the 8x rig at 8.6 percent of weight).
|
||||
|
||||
When the wave is gone the 14 standing boxes hold about 95 percent of the weight, so from then until public hash arrives the
|
||||
fleet alone is the devnet's finality: a home miner's lock lands only while the fleet is up. What holds it up: every standing
|
||||
box runs under `box-standing.sh`, which restarts a dead node within one of its 60-second passes (the hub's three deaths
|
||||
tonight: 63 s, 41 s and 56 s to the restart line), restarts the miner with the node, prunes the prover's exports and trims
|
||||
the node log, and runs the exec recovery recipe when the state layer reads zero; `lib/standing.py loop` re-rents a dead host
|
||||
in the same shape and reports a box behind its wanted binary.
|
||||
|
||||
| Tier | What it means |
|
||||
|---|---|
|
||||
| Home miner | your lock depends on 14 rented cards staying up and mining; a finality pause is not your node's fault and nothing you can fix; the rule above is what keeps it from recurring on the fleet's side |
|
||||
| Rig | the same, and a rig that leaves is itself a weight removal: at 459 MH/s on tonight's devnet it is about 20 percent of the weight, over the hour's budget by itself |
|
||||
| Pool | a pool node is one voter carrying its members' whole weight; a pool restart is the largest single removal on the network and must be sliced like the fleet's |
|
||||
| The network | finality by miner weight is only as steady as the miners' uptime; until public hash dwarfs the fleet, the fleet's supervisor is a consensus component |
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ A proof-of-work chain mined on consumer GPUs, where the same cards prove every I
|
|||
| Item | What it is | Status today |
|
||||
|---|---|---|
|
||||
| Proofs for your chain | Your batches or blocks proven by Igneum's GPU prover network and returned to the address you name | Designed. No shard has been proven on a card yet |
|
||||
| Price in dollars | Jobs are priced in dollars per proof. At launch the fee is paid on your own chain, in your currency, to a payout contract keyed by miner address, because Igneum cannot yet see your chain. Settlement in the coin follows when the proof bridge exists | Spec section 5.4 |
|
||||
| Price in dollars | Jobs are priced in dollars per proof, at or above the subsidy the prover forgoes while it proves, which is a formula with network hash as the input, never a fixed number: per shard, (card hash ÷ network hash) × 0.8 × 31.688 IGN × shard seconds, plus electricity (under a cent per billion cycles on every card). The price falls as one over network hash: at the devnet's 1.16 GH/s a quote is 100 to 300x the published market rate, and a card proving beside its miner is competitive near 100 GH/s (the Horizon economy lane, `docs/analysis/horizon/economy-and-utility.md` sections 3.1 and 4.1, 6 October 2026; approximate beyond the one card measured; ledger E20). At launch the fee is paid on your own chain, in your currency, to a payout contract keyed by miner address, because Igneum cannot yet see your chain. Settlement in the coin follows when the proof bridge exists | Spec section 5.4 |
|
||||
| Delivery rule | A job is claimed with a bond that is slashed on a late or bad proof; a job nobody proves by its deadline expires and refunds in full. At launch your chain's own bond and slashing apply to the miner who claimed the job | Design document, execution layer, sections 5.2 and 6. Bond size and timeout are open (O-5.6) |
|
||||
| A versioned interface | Jobs run against the `ProofSystem` trait, version 1 of which is SP1. A later version is a release with its own test-vector set and a three-month overlap, so your integration survives a prover swap | Design document, execution layer, section 5.6 |
|
||||
| Verification you can run | A job proof is a single proof your contract verifies on your own chain; Igneum's own segment proofs recursively verify it, so no relayer or committee is in the path | Designed |
|
||||
|
|
@ -25,7 +25,7 @@ One row per route, so operator income and protocol income never blur. Rows 1 to
|
|||
| 1. Emission, per block | IGN, new coins on the published schedule | 80% the block's miner, 20% the proving pool for the provers of that block | None | None. Implemented in consensus on the devnet |
|
||||
| 2. Base fee, both gas dimensions | IGN | Nobody | The base fee the chain sets per block | All of it. Implemented on the devnet |
|
||||
| 3. Priority fee | IGN | 80% the block's miner and provers; 20% the apps whose code ran, per call frame | The tip the sender sets | The share of any frame in an unregistered contract. Implemented on the devnet |
|
||||
| 4. External job, at launch | Your currency, on your chain | The miner who delivered, through a payout contract keyed by miner address | Priced in dollars per proof; your chain's own bond and slashing apply | None; Igneum cannot see the payment. Designed |
|
||||
| 4. External job, at launch | Your currency, on your chain | The miner who delivered, through a payout contract keyed by miner address | Priced in dollars per proof, at or above the subsidy the prover forgoes (the formula in network hash in the "Price in dollars" row, never a fixed number); your chain's own bond and slashing apply | None; Igneum cannot see the payment. Designed |
|
||||
| 5. External job, after the proof bridge | IGN, on Igneum | 90% the provers who delivered | The job fee | 10%. Designed, phase two |
|
||||
| 6. The official client's dev fee | IGN | The project, as operator income, never the protocol | 1 block template in 100 requested with the dev address; off with one flag | None. Implemented, measured on a test network 4 October 2026 |
|
||||
|
||||
|
|
|
|||
90
docs/community/discord-hooks.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Discord webhooks: the bot's posts to #numbers, #announcements and #incidents
|
||||
|
||||
6 October 2026. Three channel webhooks named "Igneum" (the Bot role in the server kit, docs/community/reddit/discord.md)
|
||||
carry the project's numbers to the Discord server. One script, `tools/community/discord-hooks.mjs` (Node 22, standard
|
||||
library only), shapes every post as a single embed: ember accent (#F2541B), the site icon as thumbnail, plain words, at most
|
||||
eight fields, UK time in the footer with UTC in brackets, never a mention, never a reply. Dry run is the default.
|
||||
|
||||
## The posts
|
||||
|
||||
| Post | Channel | When (UK) | Content |
|
||||
|---|---|---|---|
|
||||
| Network pulse | #numbers | 03:00, 09:00, 15:00, 21:00 | chain block and blocks/s over the 6 h window, hash rate and active vote keys ("a card runs several"), difficulty and its 6 h change, last lock (index, share of weight, age, voters), shards paid in the window and IGN paid to provers in the last 5 min, median proof lag and provers, reward and ramp, miner version share when /api/live exposes engines, Devnet 2 in one line (boxes, height, last gate PASS or FAIL) |
|
||||
| Daily digest | #numbers | 09:00 | a small monospace table, yesterday against now, with the 24 h deltas |
|
||||
| Weekly numbers | #numbers | Monday 09:00 | the reddit kit's template (docs/community/reddit/posts/02-weekly-numbers-template.md) as six fields with the last-week column, a link to the ledger |
|
||||
| Release | #announcements | on a publish, by the shipper | version, three download links with full sha256, node commit, consensus digest, up to five "what changed" lines read from the release plan |
|
||||
| Incident open / resolve | #incidents | by hand, or by the watcher | UTC time, what happened, who is affected per tier, what is being done; then cause, the rule or fix, duration |
|
||||
| Network feed | #network-feed | every hour, when `DISCORD_WEBHOOK_FEED` is set | height and blocks/s over the hour, hash rate and difficulty, keys active and voters, the last lock; the daily hash-origin field once per report date from `/api/live state.hash_origin`; milestones once each (every 100,000 blocks, 10,000 locks, 10,000 paid shards; the first crossing of a vote-key count and of a hash-rate step). Added 7 October 2026; docs/community/discord/structure.md section 4.1 |
|
||||
|
||||
Every number comes from the public API (`/api/stats`, `/api/live?window=300`, `/api/supply`; docs/api/public-stats.md). The
|
||||
Devnet 2 line reads the fleet file and uses its numbers and the gate word only; the seed address and the state text never pass.
|
||||
|
||||
## The watcher
|
||||
|
||||
`watch` (and every `tick`) opens one incident per condition and resolves it when the condition has been clear for 2 minutes.
|
||||
One open incident per condition at a time; the state file carries since / clear_since / open.
|
||||
|
||||
| Condition | Opens when | Text |
|
||||
|---|---|---|
|
||||
| finality_paused | `finality.active` false, or the locked index unchanged, for over 5 min | finality paused; miners and provers paid as usual; nothing final until weight returns |
|
||||
| proof_lag | `proving.median_proof_lag_s` over 900 s | proving behind; blocks final as usual, proofs late |
|
||||
| observer_silent | `stale` with `age_s` over 180 s, or the API unreachable for over 3 min | the live numbers are stale; the chain is unaffected |
|
||||
|
||||
The watcher's texts are fixed sentences in the script, each a stated rule, and are the only incidents nobody typed.
|
||||
|
||||
## Commands
|
||||
|
||||
```
|
||||
node tools/community/discord-hooks.mjs pulse | digest | weekly [--live] [--force]
|
||||
node tools/community/discord-hooks.mjs feed [--live] [--via updates] the hourly feed; --via updates posts it through DISCORD_WEBHOOK_UPDATES until #network-feed has a webhook
|
||||
node tools/community/discord-hooks.mjs release 0.3.14 --windows <url> --windows-sha <hex> --mac <url> --mac-sha <hex> --hive <url> --hive-sha <hex> \
|
||||
--node-commit 4c6b129d --digest "b18ed271 (thirteen fields, unchanged)" --plan docs/plans/release-0.3.14.md --section "1. Why" [--changed "line"]... [--live]
|
||||
node tools/community/discord-hooks.mjs incident open --what "..." --affected "..." --doing "..." [--at 2026-10-06T15:44:00Z] [--id inc-...] [--live]
|
||||
node tools/community/discord-hooks.mjs incident resolve --id inc-... --cause "..." --fix "..." [--at ...] [--live]
|
||||
node tools/community/discord-hooks.mjs watch [--live] one watcher pass
|
||||
node tools/community/discord-hooks.mjs tick [--live] one scheduler pass (what the London clock says is due, then watch)
|
||||
node tools/community/discord-hooks.mjs preview rebuild tools/community/out/preview.html
|
||||
node tools/community/discord-hooks.mjs check which webhooks are configured (names, never values)
|
||||
node --test tools/community/discord-hooks.test.mjs
|
||||
```
|
||||
|
||||
Dry run writes `tools/community/out/<key>.json` (git-ignored) and renders `out/preview.html`, a Discord-like dark page of
|
||||
every payload in the folder. The release subcommand takes the "what changed" lines from `--changed` flags or from the named
|
||||
section of the release plan: bullet lines as they are, table rows by their first cell, cut at the first semicolon, a long
|
||||
parenthetical dropped, 160 characters at most; a line that trips the guard is dropped with a note on stderr.
|
||||
|
||||
## Guard rails
|
||||
|
||||
| Rail | How |
|
||||
|---|---|
|
||||
| Forbidden strings | before every post: the founder's name and logins, hosting providers, internal host names, machine ids, internal paths, IP addresses, a standalone 32-hex token (a sha256 passes), any dl.igneum.network path outside /public/, a webhook URL, any mention; the post is refused with the pattern named |
|
||||
| Limits | title 256, description 4,096, field value 1,024, eight fields, 6,000 in total, content 2,000; refused, never cut silently |
|
||||
| Idempotency | a post id file (`posts` in the state) keyed `pulse:<date>:<hour>`, `digest:<date>`, `weekly:<date>`, `release:<version>`, `incident:<id>:open|resolve`; a rerun is skipped, `--force` posts again |
|
||||
| 429 | exponential backoff from 1 s, doubling to 60 s, `retry_after` honoured, six tries, then exit 1 |
|
||||
| State | written only after a successful live post; a dry run keeps its own state in out/state.json |
|
||||
| Mentions | `allowed_mentions: {parse: []}` on every payload and a guard that refuses @everyone, @here and user mentions in the text |
|
||||
|
||||
## Credentials and deployment
|
||||
|
||||
`~/.config/igneum/discord`, mode 600, KEY=VALUE lines: `DISCORD_WEBHOOK_NUMBERS`, `DISCORD_WEBHOOK_ANNOUNCEMENTS`,
|
||||
`DISCORD_WEBHOOK_INCIDENTS`; optional `DISCORD_WEBHOOK_FEED` (no key, no feed, one "feed off" note per tick) and
|
||||
`DISCORD_WEBHOOK_UPDATES` (the hidden updates channel, the `--via updates` route). Never in the repository, never printed;
|
||||
`check` prints which keys are set.
|
||||
|
||||
The scheduler runs on igneum-build-1 because the Mac sleeps: `infra/build-server/discord-hooks/{igneum-discord-hooks.service,
|
||||
igneum-discord-hooks.timer, install.sh}`, a tick every minute. `install.sh` copies the script to `/srv/discord-hooks/bin`, the
|
||||
credentials file to `/srv/discord-hooks/env` (mode 600, owner build, over ssh stdin) and enables the timer. The repository rule
|
||||
says the box never holds a secret; a webhook URL is one, so the script refuses unless `IGNEUM_SECRET_ON_BOX_OK=1` is set,
|
||||
which records the coordinator's ruling. A leaked webhook is deleted in Discord and the file replaced.
|
||||
|
||||
## Tested, 6 October 2026
|
||||
|
||||
Dry run against the live API at 18:51 UTC rendered all six posts (pulse 641 characters, digest 477, weekly 1,699, release
|
||||
1,501, incident open 716 and resolve 659). The guard refused the fleet file's seed address and a line with an internal path
|
||||
from the release plan. Finality was paused at render time (`finality.active` false, last lock 6842 at about 18:42Z, the newest
|
||||
checkpoints at 55% of total weight): the watcher's first condition, seen in dry run. Not yet run: a live post (no credentials
|
||||
file yet), the install on the box.
|
||||
|
||||
## The server invite
|
||||
|
||||
The standing invite is https://discord.gg/igneum (created 6 October 2026, main; the vanity names discord.gg/igneum and discord.gg/igneumnetwork were free on 3 October and are not yet claimed). The home page's join line and any public text use this URL; nobody asks for it again.
|
||||
68
docs/community/discord/README.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Discord server assets and boost perks
|
||||
|
||||
Server: Igneum (id 1557085229330464808), vanity https://discord.gg/igneum, boosted to level 3 on 7 October 2026 (20 boosts: 14 on the three levels, 3 on the Server Tag add-on, 3 on the Enhanced Role Styles add-on; 0 spare).
|
||||
|
||||
Every file in `assets/` is built from the brand in this repo: the ember mark from `site/index.html`, the colour tokens from `site/site.css` (obsidian #0C0C0E, ember #F2541B, ember-hi #FF6A2B, molten #FFB35C, bone #F4F1EC, ink-2 #C9C7C2, ash #9A9A9E), the fonts in `site/fonts/` (Unbounded for the wordmark, IBM Plex Sans and Mono for the rest) and the DAG step recording `docs/plans/site-ui-3-shots/after-v2/home-steps-1440-dark.webm`.
|
||||
|
||||
Rebuild everything from the repo root:
|
||||
|
||||
```
|
||||
python3 docs/community/discord/make-assets.py [path to a frame of the recording for the invite backdrop]
|
||||
sh docs/community/discord/make-banner-gif.sh
|
||||
```
|
||||
|
||||
The guide banner (1920x480) was rendered with the same helpers; see the commit that added it for the snippet.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Size | Purpose | Where it is set |
|
||||
| --- | --- | --- | --- |
|
||||
| `server-banner-960x540.gif` | 5.9 MB | Animated server banner: nine seconds of the live DAG scene between two dark bands that carry the lockup and the tagline (level 3 perk, limit 10 MB) | Server Settings, Boost Perks, Server Banner Background |
|
||||
| `server-banner-960x540.png` | 42 KB | Static fallback for the same slot | Not uploaded (the GIF is live) |
|
||||
| `banner-overlay-960x540.png` | 16 KB | Transparent overlay ffmpeg composites onto the recording for the GIF | Build input only |
|
||||
| `invite-background-1920x1080.png` | 368 KB | Invite embed and invite page background: lockup and tagline over a blurred, darkened DAG frame | Server Settings, Boost Perks, Server Invite Background |
|
||||
| `guide-banner-1920x480.png` | 69 KB | Server Guide header (4:1) | Server Settings, Onboarding, Server Guide, Server Guide Banner |
|
||||
| `role-*.png` | 2 to 5 KB each, 64x64 | Role icons, one mark variant per role (limit 256 KB) | Server Settings, Roles, each role's Display tab |
|
||||
| `emoji-*.png` | 2 to 8 KB each, 128x128 | The ten custom emoji | Server Settings, Emoji |
|
||||
| `sticker-*.png` | 9 to 24 KB each, 320x320 | The three stickers (limit 512 KB) | Server Settings, Stickers |
|
||||
| `make-assets.py` | | Generator for every PNG above | |
|
||||
| `make-banner-gif.sh` | | ffmpeg recipe for the animated banner | |
|
||||
|
||||
## Role ladder (7 October 2026)
|
||||
|
||||
| Role | Colour | Style | Icon | Hoisted | Permissions |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Founder | unchanged | solid | `role-founder.png` (molten mark on obsidian) | unchanged | untouched |
|
||||
| Core | unchanged | solid | `role-core.png` (ember-hi mark on obsidian) | unchanged | untouched |
|
||||
| Pool operator | #FF6A2B | gradient ember to molten | `role-pool-operator.png` (mark in a molten ring) | yes | none |
|
||||
| Node runner | #F4F1EC | gradient ember to molten | `role-node-runner.png` (bone mark) | yes | none |
|
||||
| Verified miner | #F2541B | gradient ember to molten | `role-verified-miner.png` (ember mark with a tick) | yes | none |
|
||||
| Miner | #F2541B | gradient ember to molten | `role-miner.png` (ember mark) | yes | unchanged |
|
||||
| Early miner | #FFB35C | solid | `role-early-miner.png` (obsidian mark on molten) | no | none |
|
||||
| Prover | #FFB35C | gradient ember to molten | `role-prover.png` (molten mark) | no | unchanged |
|
||||
| Builder | #E8E4DC | solid | `role-builder.png` (bone mark on graphite) | no | Embed Links, Attach Files |
|
||||
| Researcher | #9A9A9E | solid | `role-researcher.png` (ash mark) | no | Embed Links, Attach Files |
|
||||
| Community | #C9C7C2 | solid | `role-community.png` (outlined mark) | no | none |
|
||||
| Bot | #9A9A9E | solid | `role-bot.png` (ash mark on row) | no | unchanged |
|
||||
|
||||
"None" means the role grants nothing of its own; members fall back to @everyone. Early miner is for the first 1,000 miners. Gradient roles use the Enhanced Role Styles add-on with start #F2541B and end #FFB35C. Holographic was tried on Founder and left off: it is a fixed pink and blue shimmer with no colour control and does not read as the brand.
|
||||
|
||||
List order (set by drag on 7 October 2026): Founder, Core, Pool operator, Node runner, Verified miner, Prover, Miner, Early miner, Builder, Researcher, Community, Bot, Server Booster.
|
||||
|
||||
## Emoji and stickers
|
||||
|
||||
Emoji names: `ign_ember`, `ign_block`, `ign_shard`, `ign_lock`, `ign_gpu`, `ign_proven`, `ign_hash`, `ign_letter`, `ign_flame`, `ign_ladder`.
|
||||
|
||||
Stickers: Ember (related emoji fire), Proven (white_check_mark), GPU (desktop_computer). Three of the five free slots are used.
|
||||
|
||||
## Onboarding
|
||||
|
||||
Server Guide: welcome sign plus five to-dos (start-here, mining, ledger, announcements, read the rules) and the guide banner. Pre-join question "What do you mine with?" with five answers: NVIDIA, AMD and Apple grant Miner and #mining; "I run a node" grants Node runner and #devnet; "I build" grants Builder and #proving. Multiple answers allowed, not required.
|
||||
|
||||
## Server Tag
|
||||
|
||||
IGNM, enabled 7 October 2026 on the 3-boost add-on. Badge: Fire (Discord offers only its own pixel-art badge set, no custom upload, so the ember mark cannot be the badge; Fire is the nearest). Badge colour: custom, primary #F2541B. Members adopt the tag from Server Settings, Server Tag ("Adopt Tag") or from their own profile; the founder's account has not adopted it yet.
|
||||
|
||||
## Not set, and why
|
||||
|
||||
- Server Profile banner: that field only offers colour presets; it stays on "Server Icon Colour", which derives from the ember icon.
|
||||
16
docs/community/discord/announcement-01.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# #announcements, post 1 (posted on the 0.3.15 live line through the Igneum webhook)
|
||||
|
||||
Welcome to Igneum.
|
||||
|
||||
Igneum is a proof-of-work chain built for graphics cards. The miners also prove every block with zero-knowledge proofs and are paid for it from the block. No premine, no stake, no fee to any team in the protocol. One founder, pseudonymous, building with AI systems; every criticism we know of is logged at igneum.network/ledger.
|
||||
|
||||
**What is running today**
|
||||
The devnet has mined since 3 October 2026. Igneum Miner 0.3.15 is the current app for Windows, macOS and Linux (HiveOS package included): download at igneum.network/miner. Coins on the devnet have no value and the chain may be reset. Nothing is for sale.
|
||||
|
||||
**0.3.15**
|
||||
A new app: an Overview page with the live chain and your own blocks lit, a Cards page with Ember Tune woven into every card, and plain words everywhere. Under it, the first consensus change made by miner signal: when 95% of mining weight runs the new program, class v4 switches on by itself. It costs an RTX 5090 about 80 W for 0.2% of its rate, costs a 4070 and a 9070 XT nothing, and in our public model it cuts the strongest chip's edge from about 5.6x to about 2x per joule. The rehearsal on 52 rented nodes flipped together tonight with zero rejected blocks.
|
||||
|
||||
**How this channel works**
|
||||
Releases and chain events only. Numbers every six hours in #numbers from the labelled bot. Incidents, with cause and fix, in #incidents. Questions in #ask, criticism in #ledger, where it gets a ledger id and a date.
|
||||
|
||||
igneum.network · /litepaper · /live · /miners · /ledger · /evidence
|
||||
BIN
docs/community/discord/assets/banner-overlay-960x540.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/community/discord/assets/emoji-block.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
docs/community/discord/assets/emoji-flame.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
docs/community/discord/assets/emoji-gpu.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
docs/community/discord/assets/emoji-hash.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
docs/community/discord/assets/emoji-igneum.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
docs/community/discord/assets/emoji-ladder.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
docs/community/discord/assets/emoji-letter.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
docs/community/discord/assets/emoji-lock.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
docs/community/discord/assets/emoji-proven.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
docs/community/discord/assets/emoji-shard.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/community/discord/assets/guide-banner-1920x480.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/community/discord/assets/invite-background-1920x1080.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
docs/community/discord/assets/role-bot.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
docs/community/discord/assets/role-builder.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
docs/community/discord/assets/role-community.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
docs/community/discord/assets/role-core.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
docs/community/discord/assets/role-early-miner.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
docs/community/discord/assets/role-founder.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
docs/community/discord/assets/role-miner.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
docs/community/discord/assets/role-node-runner.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
docs/community/discord/assets/role-pool-operator.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
docs/community/discord/assets/role-prover.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
docs/community/discord/assets/role-researcher.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
docs/community/discord/assets/role-verified-miner.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
docs/community/discord/assets/server-banner-960x540.gif
Normal file
|
After Width: | Height: | Size: 5.6 MiB |
BIN
docs/community/discord/assets/server-banner-960x540.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
docs/community/discord/assets/sticker-ember.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
docs/community/discord/assets/sticker-gpu.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/community/discord/assets/sticker-proven.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
379
docs/community/discord/make-assets.py
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Build the Discord boost assets from the brand (mark, fonts, tokens in site/site.css).
|
||||
|
||||
Run from the repo root: python3 docs/community/discord/make-assets.py
|
||||
Writes into docs/community/discord/assets/. The animated banner is built by ffmpeg from
|
||||
docs/plans/site-ui-3-shots/after-v2/home-steps-1440-dark.webm (see make-banner-gif.sh).
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
OUT = os.path.join(ROOT, "docs", "community", "discord", "assets")
|
||||
FONTS = os.path.join(ROOT, "site", "fonts")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
# site.css tokens (dark theme)
|
||||
OBSIDIAN = (12, 12, 14)
|
||||
GRAPHITE = (22, 22, 26)
|
||||
ROW = (17, 17, 20)
|
||||
LINE = (42, 42, 48)
|
||||
LINE2 = (58, 58, 66)
|
||||
EMBER = (242, 84, 27)
|
||||
EMBER_HI = (255, 106, 43)
|
||||
MOLTEN = (255, 179, 92)
|
||||
BONE = (244, 241, 236)
|
||||
INK2 = (201, 199, 194)
|
||||
ASH = (154, 154, 158)
|
||||
|
||||
# The ember mark, from site/index.html (viewBox 100 units)
|
||||
OUTER = [(50, 4), (74, 34), (67, 58), (80, 54), (61, 96), (39, 96), (20, 54), (33, 58), (26, 34)]
|
||||
INNER = [(50, 42), (59, 58), (50, 82), (41, 58)]
|
||||
SS = 4 # supersample
|
||||
|
||||
|
||||
def font(name, size):
|
||||
return ImageFont.truetype(os.path.join(FONTS, name + ".woff2"), size)
|
||||
|
||||
|
||||
def rgba(color, a=255):
|
||||
return tuple(color) + (a,)
|
||||
|
||||
|
||||
def mark(size, color=EMBER, cut=None, box=None):
|
||||
"""Return an RGBA image of the ember mark. `cut` fills the inner diamond (None = transparent)."""
|
||||
W = size * SS
|
||||
im = Image.new("RGBA", (W, W), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(im)
|
||||
if box is not None:
|
||||
d.rounded_rectangle([0, 0, W - 1, W - 1], radius=int(W * 0.16), fill=rgba(box))
|
||||
pad = 0.12 if box is None else 0.19
|
||||
s = W * (1 - 2 * pad) / 100.0
|
||||
o = W * pad
|
||||
pts = [(o + x * s, o + y * s) for x, y in OUTER]
|
||||
d.polygon(pts, fill=rgba(color))
|
||||
inner = [(o + x * s, o + y * s) for x, y in INNER]
|
||||
if cut is None:
|
||||
hole = Image.new("L", (W, W), 0)
|
||||
ImageDraw.Draw(hole).polygon(inner, fill=255)
|
||||
alpha = im.getchannel("A")
|
||||
alpha = Image.composite(Image.new("L", (W, W), 0), alpha, hole)
|
||||
im.putalpha(alpha)
|
||||
else:
|
||||
d.polygon(inner, fill=rgba(cut))
|
||||
return im.resize((size, size), Image.LANCZOS)
|
||||
|
||||
|
||||
def text_size(d, txt, f):
|
||||
l, t, r, b = d.textbbox((0, 0), txt, font=f)
|
||||
return r - l, b - t, l, t
|
||||
|
||||
|
||||
def lockup(width, mark_px, word_px, word_color=BONE, mark_color=EMBER, gap=None):
|
||||
"""Mark + IGNEUM wordmark on a transparent strip, returned as RGBA with the strip height."""
|
||||
f = font("unbounded-900", word_px)
|
||||
probe = ImageDraw.Draw(Image.new("RGBA", (10, 10)))
|
||||
tw, th, tl, tt = text_size(probe, "IGNEUM", f)
|
||||
gap = gap or int(mark_px * 0.32)
|
||||
H = max(mark_px, th + 8)
|
||||
im = Image.new("RGBA", (mark_px + gap + tw + 4, H), (0, 0, 0, 0))
|
||||
m = mark(mark_px, mark_color)
|
||||
im.alpha_composite(m, (0, (H - mark_px) // 2))
|
||||
d = ImageDraw.Draw(im)
|
||||
d.text((mark_px + gap - tl, (H - th) // 2 - tt), "IGNEUM", font=f, fill=rgba(word_color))
|
||||
return im
|
||||
|
||||
|
||||
def glow(base, mark_px, cx, cy, color=EMBER, spread=1.6, alpha=70):
|
||||
g = Image.new("RGBA", base.size, (0, 0, 0, 0))
|
||||
r = int(mark_px * spread / 2)
|
||||
ImageDraw.Draw(g).ellipse([cx - r, cy - r, cx + r, cy + r], fill=rgba(color, alpha))
|
||||
g = g.filter(ImageFilter.GaussianBlur(mark_px * 0.45))
|
||||
base.alpha_composite(g)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- banners
|
||||
def banner_static(W, H, name, mark_px, word_px, tag_px, sub_px, backdrop=None):
|
||||
im = Image.new("RGBA", (W, H), rgba(OBSIDIAN))
|
||||
if backdrop is not None:
|
||||
bd = Image.open(backdrop).convert("RGB")
|
||||
# crop the recording frame to 16:9 and darken it
|
||||
bw, bh = bd.size
|
||||
ch = int(bw * H / W)
|
||||
bd = bd.crop((0, 60, bw, 60 + ch)).resize((W, H), Image.LANCZOS)
|
||||
bd = bd.filter(ImageFilter.GaussianBlur(W * 0.004))
|
||||
bd = Image.blend(Image.new("RGB", (W, H), OBSIDIAN), bd, 0.34)
|
||||
im.paste(bd, (0, 0))
|
||||
scrim = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||||
sd = ImageDraw.Draw(scrim)
|
||||
for y in range(H):
|
||||
a = int(255 * (0.25 + 0.55 * (y / H) ** 1.4))
|
||||
sd.line([(0, y), (W, y)], fill=rgba(OBSIDIAN, min(255, a)))
|
||||
im.alpha_composite(scrim)
|
||||
lk = lockup(W, mark_px, word_px)
|
||||
cx = (W - lk.width) // 2
|
||||
cy = int(H * 0.40) - lk.height // 2
|
||||
glow(im, mark_px, cx + mark_px // 2, cy + lk.height // 2)
|
||||
im.alpha_composite(lk, (cx, cy))
|
||||
d = ImageDraw.Draw(im)
|
||||
f_tag = font("unbounded-700", tag_px)
|
||||
tw, th, tl, tt = text_size(d, "Mined by GPUs. Proven by fire.", f_tag)
|
||||
ty = cy + lk.height + int(H * 0.06)
|
||||
d.text(((W - tw) // 2 - tl, ty - tt), "Mined by GPUs. Proven by fire.", font=f_tag, fill=rgba(MOLTEN))
|
||||
f_sub = font("plex-sans-500", sub_px)
|
||||
sub = "The GPU-mined layer 1 · igneum.network"
|
||||
sw, sh, sl, st = text_size(d, sub, f_sub)
|
||||
d.text(((W - sw) // 2 - sl, ty + th + int(H * 0.035) - st), sub, font=f_sub, fill=rgba(INK2))
|
||||
path = os.path.join(OUT, name)
|
||||
im.convert("RGB").save(path, optimize=True)
|
||||
return path
|
||||
|
||||
|
||||
def banner_overlay(W, H, name, mark_px, word_px, band):
|
||||
"""Transparent overlay for the animated banner: the graph sits between two dark bands
|
||||
(ffmpeg pads it); the lockup goes in the top band, the tagline in the bottom band."""
|
||||
im = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(im)
|
||||
lk = lockup(W, mark_px, word_px)
|
||||
x = int(W * 0.035)
|
||||
im.alpha_composite(lk, (x, (band - lk.height) // 2))
|
||||
f_tag = font("unbounded-700", int(word_px * 0.62))
|
||||
tw, th, tl, tt = text_size(d, "Mined by GPUs. Proven by fire.", f_tag)
|
||||
d.text((x - tl, H - band + (band - th) // 2 - tt), "Mined by GPUs. Proven by fire.", font=f_tag, fill=rgba(MOLTEN))
|
||||
f_sub = font("plex-sans-500", int(word_px * 0.5))
|
||||
sw, sh, sl, st = text_size(d, "igneum.network", f_sub)
|
||||
d.text((W - x - sw - sl, H - band + (band - sh) // 2 - st), "igneum.network", font=f_sub, fill=rgba(INK2))
|
||||
path = os.path.join(OUT, name)
|
||||
im.save(path, optimize=True)
|
||||
return path
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- role icons
|
||||
def role_icons():
|
||||
out = {}
|
||||
spec = {
|
||||
"founder": dict(color=MOLTEN, box=OBSIDIAN),
|
||||
"core": dict(color=EMBER_HI, box=OBSIDIAN),
|
||||
"miner": dict(color=EMBER),
|
||||
"prover": dict(color=MOLTEN),
|
||||
"pool-operator": dict(color=EMBER_HI, ring=True),
|
||||
"node-runner": dict(color=BONE),
|
||||
"verified-miner": dict(color=EMBER, tick=True),
|
||||
"early-miner": dict(color=OBSIDIAN, box=MOLTEN),
|
||||
"builder": dict(color=BONE, box=GRAPHITE),
|
||||
"researcher": dict(color=ASH),
|
||||
"community": dict(color=INK2, outline=True),
|
||||
"bot": dict(color=ASH, box=ROW),
|
||||
}
|
||||
for name, s in spec.items():
|
||||
size = 64
|
||||
W = size * SS
|
||||
if s.get("outline"):
|
||||
im = Image.new("RGBA", (W, W), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(im)
|
||||
pad = 0.12
|
||||
sc = W * (1 - 2 * pad) / 100.0
|
||||
o = W * pad
|
||||
pts = [(o + x * sc, o + y * sc) for x, y in OUTER]
|
||||
d.line(pts + [pts[0]], fill=rgba(s["color"]), width=int(W * 0.055), joint="curve")
|
||||
inner = [(o + x * sc, o + y * sc) for x, y in INNER]
|
||||
d.polygon(inner, fill=rgba(s["color"]))
|
||||
im = im.resize((size, size), Image.LANCZOS)
|
||||
else:
|
||||
im = mark(size, s["color"], cut=(s["box"] if s.get("box") else None), box=s.get("box"))
|
||||
if s.get("ring"):
|
||||
big = im.resize((W, W), Image.LANCZOS)
|
||||
d = ImageDraw.Draw(big)
|
||||
d.ellipse([W * 0.02, W * 0.02, W * 0.98, W * 0.98], outline=rgba(MOLTEN), width=int(W * 0.05))
|
||||
im = big.resize((size, size), Image.LANCZOS)
|
||||
if s.get("tick"):
|
||||
big = im.resize((W, W), Image.LANCZOS)
|
||||
d = ImageDraw.Draw(big)
|
||||
r = W * 0.19
|
||||
cx, cy = W * 0.80, W * 0.80
|
||||
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=rgba(MOLTEN))
|
||||
d.line([(cx - r * 0.5, cy), (cx - r * 0.1, cy + r * 0.42), (cx + r * 0.55, cy - r * 0.42)],
|
||||
fill=rgba(OBSIDIAN), width=int(W * 0.045), joint="curve")
|
||||
im = big.resize((size, size), Image.LANCZOS)
|
||||
path = os.path.join(OUT, "role-%s.png" % name)
|
||||
im.save(path, optimize=True)
|
||||
out[name] = path
|
||||
return out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- emoji
|
||||
def emoji_canvas(size=128):
|
||||
W = size * SS
|
||||
return Image.new("RGBA", (W, W), (0, 0, 0, 0)), W
|
||||
|
||||
|
||||
def finish(im, size, path):
|
||||
im.resize((size, size), Image.LANCZOS).save(path, optimize=True)
|
||||
return path
|
||||
|
||||
|
||||
def emoji_set():
|
||||
size = 128
|
||||
paths = {}
|
||||
|
||||
# 1 the ember
|
||||
paths["igneum"] = mark(size, EMBER)
|
||||
paths["igneum"].save(os.path.join(OUT, "emoji-igneum.png"), optimize=True)
|
||||
paths["igneum"] = os.path.join(OUT, "emoji-igneum.png")
|
||||
|
||||
# 2 a block (the DAG square: rounded outline, ember, dark fill)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
m = W * 0.12
|
||||
d.rounded_rectangle([m, m, W - m, W - m], radius=int(W * 0.14), fill=rgba(GRAPHITE),
|
||||
outline=rgba(EMBER), width=int(W * 0.085))
|
||||
paths["block"] = finish(im, size, os.path.join(OUT, "emoji-block.png"))
|
||||
|
||||
# 3 a shard (a tall molten crystal with one lit facet)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
p = [(0.50, 0.06), (0.78, 0.40), (0.62, 0.94), (0.38, 0.94), (0.22, 0.40)]
|
||||
d.polygon([(x * W, y * W) for x, y in p], fill=rgba(MOLTEN))
|
||||
d.polygon([(x * W, y * W) for x, y in [(0.50, 0.06), (0.78, 0.40), (0.62, 0.94), (0.50, 0.40)]], fill=rgba(EMBER))
|
||||
paths["shard"] = finish(im, size, os.path.join(OUT, "emoji-shard.png"))
|
||||
|
||||
# 4 a lock (ember body, bone shackle, dark keyhole)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
sw = int(W * 0.09)
|
||||
d.arc([W * 0.26, W * 0.06, W * 0.74, W * 0.60], 180, 360, fill=rgba(BONE), width=sw)
|
||||
d.line([(W * 0.26 + sw / 2, W * 0.33), (W * 0.26 + sw / 2, W * 0.50)], fill=rgba(BONE), width=sw)
|
||||
d.line([(W * 0.74 - sw / 2, W * 0.33), (W * 0.74 - sw / 2, W * 0.50)], fill=rgba(BONE), width=sw)
|
||||
d.rounded_rectangle([W * 0.14, W * 0.46, W * 0.86, W * 0.94], radius=int(W * 0.10), fill=rgba(EMBER))
|
||||
d.ellipse([W * 0.43, W * 0.60, W * 0.57, W * 0.74], fill=rgba(OBSIDIAN))
|
||||
d.rounded_rectangle([W * 0.465, W * 0.68, W * 0.535, W * 0.84], radius=int(W * 0.03), fill=rgba(OBSIDIAN))
|
||||
paths["lock"] = finish(im, size, os.path.join(OUT, "emoji-lock.png"))
|
||||
|
||||
# 5 a GPU (graphite card, two ember fans, bone bracket)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
d.rounded_rectangle([W * 0.06, W * 0.24, W * 0.94, W * 0.78], radius=int(W * 0.08), fill=rgba(GRAPHITE),
|
||||
outline=rgba(LINE2), width=int(W * 0.03))
|
||||
d.rectangle([W * 0.06, W * 0.78, W * 0.16, W * 0.90], fill=rgba(BONE))
|
||||
d.rectangle([W * 0.20, W * 0.74, W * 0.80, W * 0.80], fill=rgba(LINE2))
|
||||
for cx in (0.34, 0.66):
|
||||
r = W * 0.17
|
||||
d.ellipse([cx * W - r, W * 0.51 - r, cx * W + r, W * 0.51 + r], outline=rgba(EMBER), width=int(W * 0.045))
|
||||
for k in range(6):
|
||||
a = k * math.pi / 3
|
||||
d.line([(cx * W, W * 0.51), (cx * W + math.cos(a) * r * 0.85, W * 0.51 + math.sin(a) * r * 0.85)],
|
||||
fill=rgba(EMBER), width=int(W * 0.035))
|
||||
d.ellipse([cx * W - r * 0.22, W * 0.51 - r * 0.22, cx * W + r * 0.22, W * 0.51 + r * 0.22], fill=rgba(MOLTEN))
|
||||
paths["gpu"] = finish(im, size, os.path.join(OUT, "emoji-gpu.png"))
|
||||
|
||||
# 6 the proof tick (ember rounded square, bone tick)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
d.rounded_rectangle([W * 0.08, W * 0.08, W * 0.92, W * 0.92], radius=int(W * 0.18), fill=rgba(EMBER))
|
||||
d.line([(W * 0.28, W * 0.52), (W * 0.44, W * 0.68), (W * 0.74, W * 0.34)], fill=rgba(BONE), width=int(W * 0.10),
|
||||
joint="curve")
|
||||
paths["proven"] = finish(im, size, os.path.join(OUT, "emoji-proven.png"))
|
||||
|
||||
# 7 the hash glyph (Plex Mono)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
f = font("plex-mono-500", int(W * 0.92))
|
||||
tw, th, tl, tt = text_size(d, "#", f)
|
||||
d.text(((W - tw) / 2 - tl, (W - th) / 2 - tt), "#", font=f, fill=rgba(EMBER))
|
||||
paths["hash"] = finish(im, size, os.path.join(OUT, "emoji-hash.png"))
|
||||
|
||||
# 8 the wordmark letter (bone I on an ember square)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
d.rounded_rectangle([W * 0.08, W * 0.08, W * 0.92, W * 0.92], radius=int(W * 0.18), fill=rgba(OBSIDIAN))
|
||||
f = font("unbounded-900", int(W * 0.62))
|
||||
tw, th, tl, tt = text_size(d, "I", f)
|
||||
d.text(((W - tw) / 2 - tl, (W - th) / 2 - tt), "I", font=f, fill=rgba(EMBER))
|
||||
paths["letter"] = finish(im, size, os.path.join(OUT, "emoji-letter.png"))
|
||||
|
||||
# 9 a flame (ember outer, molten core)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
outer = [(0.50, 0.04), (0.64, 0.22), (0.70, 0.40), (0.82, 0.30), (0.88, 0.58), (0.80, 0.84), (0.62, 0.96),
|
||||
(0.38, 0.96), (0.20, 0.84), (0.12, 0.58), (0.20, 0.36), (0.32, 0.44), (0.34, 0.24)]
|
||||
d.polygon([(x * W, y * W) for x, y in outer], fill=rgba(EMBER))
|
||||
core = [(0.50, 0.46), (0.62, 0.60), (0.66, 0.78), (0.58, 0.92), (0.42, 0.92), (0.34, 0.78), (0.38, 0.60)]
|
||||
d.polygon([(x * W, y * W) for x, y in core], fill=rgba(MOLTEN))
|
||||
paths["flame"] = finish(im, size, os.path.join(OUT, "emoji-flame.png"))
|
||||
|
||||
# 10 the ladder (two ember rails, bone rungs)
|
||||
im, W = emoji_canvas()
|
||||
d = ImageDraw.Draw(im)
|
||||
rw = int(W * 0.09)
|
||||
d.line([(W * 0.28, W * 0.06), (W * 0.28, W * 0.94)], fill=rgba(EMBER), width=rw)
|
||||
d.line([(W * 0.72, W * 0.06), (W * 0.72, W * 0.94)], fill=rgba(EMBER), width=rw)
|
||||
for k in range(5):
|
||||
y = W * (0.16 + k * 0.17)
|
||||
d.line([(W * 0.28, y), (W * 0.72, y)], fill=rgba(BONE), width=int(W * 0.07))
|
||||
paths["ladder"] = finish(im, size, os.path.join(OUT, "emoji-ladder.png"))
|
||||
return paths
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- stickers (320x320)
|
||||
def stickers():
|
||||
size = 320
|
||||
paths = {}
|
||||
# 1 the ember, large
|
||||
m = mark(size, EMBER)
|
||||
p = os.path.join(OUT, "sticker-ember.png")
|
||||
m.save(p, optimize=True)
|
||||
paths["ember"] = p
|
||||
|
||||
# 2 "Proven by fire." badge
|
||||
im = Image.new("RGBA", (size * SS, size * SS), (0, 0, 0, 0))
|
||||
W = size * SS
|
||||
d = ImageDraw.Draw(im)
|
||||
d.rounded_rectangle([W * 0.03, W * 0.30, W * 0.97, W * 0.70], radius=int(W * 0.10), fill=rgba(OBSIDIAN),
|
||||
outline=rgba(EMBER), width=int(W * 0.02))
|
||||
f1 = font("unbounded-900", int(W * 0.115))
|
||||
f2 = font("unbounded-700", int(W * 0.062))
|
||||
tw, th, tl, tt = text_size(d, "PROVEN", f1)
|
||||
d.text(((W - tw) / 2 - tl, W * 0.37 - tt), "PROVEN", font=f1, fill=rgba(BONE))
|
||||
tw2, th2, tl2, tt2 = text_size(d, "by fire.", f2)
|
||||
d.text(((W - tw2) / 2 - tl2, W * 0.37 + th + W * 0.04 - tt2), "by fire.", font=f2, fill=rgba(MOLTEN))
|
||||
p = os.path.join(OUT, "sticker-proven.png")
|
||||
im.resize((size, size), Image.LANCZOS).save(p, optimize=True)
|
||||
paths["proven"] = p
|
||||
|
||||
# 3 GPU with the ember rising out of it
|
||||
im = Image.new("RGBA", (W, W), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(im)
|
||||
em = mark(int(size * 0.55), EMBER).resize((int(W * 0.55), int(W * 0.55)), Image.LANCZOS)
|
||||
im.alpha_composite(em, (int(W * 0.225), int(W * 0.02)))
|
||||
d.rounded_rectangle([W * 0.06, W * 0.52, W * 0.94, W * 0.86], radius=int(W * 0.07), fill=rgba(GRAPHITE),
|
||||
outline=rgba(LINE2), width=int(W * 0.02))
|
||||
d.rectangle([W * 0.06, W * 0.86, W * 0.16, W * 0.95], fill=rgba(BONE))
|
||||
for cx in (0.34, 0.66):
|
||||
r = W * 0.12
|
||||
cy = W * 0.69
|
||||
d.ellipse([cx * W - r, cy - r, cx * W + r, cy + r], outline=rgba(EMBER), width=int(W * 0.03))
|
||||
for k in range(6):
|
||||
a = k * math.pi / 3
|
||||
d.line([(cx * W, cy), (cx * W + math.cos(a) * r * 0.85, cy + math.sin(a) * r * 0.85)],
|
||||
fill=rgba(EMBER), width=int(W * 0.025))
|
||||
d.ellipse([cx * W - r * 0.22, cy - r * 0.22, cx * W + r * 0.22, cy + r * 0.22], fill=rgba(MOLTEN))
|
||||
p = os.path.join(OUT, "sticker-gpu.png")
|
||||
im.resize((size, size), Image.LANCZOS).save(p, optimize=True)
|
||||
paths["gpu"] = p
|
||||
return paths
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
frame = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
print(banner_static(960, 540, "server-banner-960x540.png", 150, 96, 30, 20))
|
||||
print(banner_static(1920, 1080, "invite-background-1920x1080.png", 300, 192, 60, 40, backdrop=frame))
|
||||
print(banner_overlay(960, 540, "banner-overlay-960x540.png", 46, 30, 87))
|
||||
for k, v in role_icons().items():
|
||||
print(k, v, os.path.getsize(v))
|
||||
for k, v in emoji_set().items():
|
||||
print(k, v, os.path.getsize(v))
|
||||
for k, v in stickers().items():
|
||||
print(k, v, os.path.getsize(v))
|
||||
11
docs/community/discord/make-banner-gif.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
# Animated server banner (level 3 perk): nine seconds of the DAG step recording (from 1 s, after the hero fold), graph only,
|
||||
# padded into two dark bands that carry the lockup and the tagline (banner-overlay-960x540.png).
|
||||
# Run from the repo root after make-assets.py. Output stays under Discord's 10 MB banner limit.
|
||||
set -e
|
||||
A=docs/community/discord/assets
|
||||
SRC=docs/plans/site-ui-3-shots/after-v2/home-steps-1440-dark.webm
|
||||
ffmpeg -v error -y -ss 1 -t 9 -i "$SRC" -i "$A/banner-overlay-960x540.png" -filter_complex \
|
||||
"[0:v]crop=1340:510:50:118,pad=1340:754:0:122:color=#0C0C0E,scale=960:540,fps=12[b];[b][1:v]overlay=0:0,split[a][c];[a]palettegen=max_colors=160:stats_mode=diff[p];[c][p]paletteuse=dither=bayer:bayer_scale=4:diff_mode=rectangle" \
|
||||
"$A/server-banner-960x540.gif"
|
||||
ls -la "$A/server-banner-960x540.gif"
|
||||
333
docs/community/discord/structure.md
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
# Discord server structure: the build sheet and the changelog
|
||||
|
||||
Server Igneum (id 1557085229330464808, https://discord.gg/igneum). This file is the structure half of the server; the
|
||||
cosmetics half (banner, roles ladder, emoji, stickers, server guide, pre-join question, server tag, vanity) is in
|
||||
README.md. Every item below is a row with a "set / read back" state. A row is set only when it has been read back
|
||||
from the server after the change. Nothing is posted that names a number our files do not hold.
|
||||
|
||||
The founder, 7 October 2026, 12:0x UK: "is discord fully setup and optimised to house an amazing community?" Cosmetics yes;
|
||||
structure no. This sheet is the structure.
|
||||
|
||||
## Status, 7 October 2026, 13:1x UK
|
||||
|
||||
| Item | State |
|
||||
|---|---|
|
||||
| Chrome profile | confirmed: the signed-in Google account on the tab is the igneum.network login, the Igneum profile; the Discord account is igneum_network (Founder) |
|
||||
| Discord session | expired at 11:1x UK (nothing typed); the founder signed in at 12:5x UK and the sheet was built in one pass from 12:5x to 13:1x UK |
|
||||
| Channels | 24 channels in 4 categories, every topic set, 19 pinned first posts, slowmode 10 s on every talk and support channel, threads on, read-only where the sheet says (section 1, every row "set, read back") |
|
||||
| Moderation | 7 AutoMod rules live (section 2), verification Medium, explicit filter on all members, raid protection 3 of 3, DM protection 5 of 5, prune restricted to admins, #mod-log private with every alert, rules screen on with the four lines |
|
||||
| Onboarding | pre-join answers re-pointed at the new map, 5 server-guide to-dos, welcome sign rewritten, safety notifications to #mod-log |
|
||||
| Network feed | LIVE from the box every hour: `DISCORD_WEBHOOK_FEED` created (webhook "Igneum feed" on #network-feed, 13:0x UK), written to `~/.config/igneum/discord`, `install.sh` run with the ruling flag at 13:05 UK; the box's first hourly post `feed:2026-10-07:13` landed at 13:06 UK (message 1557363371605622818) and the next tick was "0 due". The Mac's proof posts: `feed:2026-10-07:11` through the updates webhook (11:30 UK) and `feed:2026-10-07:12-proof` through the new webhook (13:05 UK, message 1557363266689171547) |
|
||||
| Not done, owed | 2FA for moderation (the founder account's 2FA is off: Discord greys the switch); the office-hour event (Discord has no undated event; the template is section 5, created the day a time is picked). Done at 13:1x UK: post 1's "How this channel works" paragraph edited through the announcements webhook (PATCH 200, edited 12:11:37 UTC); the release webhook moved to #releases |
|
||||
|
||||
## 1. Channel map
|
||||
|
||||
Order top to bottom as the sidebar shows it. "Threads" means Create Public Threads allowed for @everyone and the
|
||||
channel's own threads kept (auto-archive 3 days). Slowmode is 10 s where set. A pinned post is the channel's first
|
||||
message, posted from the founder's account (the webhook posts only the bot's numbers), then pinned.
|
||||
|
||||
### What changes from the current server
|
||||
|
||||
| Today | Becomes | Why |
|
||||
|---|---|---|
|
||||
| #numbers | #network-feed, read-only, under START HERE's bot row moved to its own place below MINE | one bot channel; the pulse, the digest, the weekly, the hash-origin report and the hourly feed all land here. `DISCORD_WEBHOOK_NUMBERS` keeps working (the webhook follows the channel through a rename); `DISCORD_WEBHOOK_FEED` is set to the same channel's URL (a second webhook named "Igneum feed", or the same URL copied) |
|
||||
| #first-blocks | #first-block | the brief's name; one block per post, so the singular reads right. ladder.md says #first-blocks; this sheet is the later word |
|
||||
| #ask | removed | the four support channels and #mining take questions; the pinned FAQ answers the common ones; #ask's messages are read once for anything worth pinning before deletion |
|
||||
| #wallet | removed | wallet questions go to the support channel of the OS; the wallet page is linked from every support pin |
|
||||
| #incidents | kept, under START HERE after #releases | the watcher already posts there; cause and fix in public is the ledger's rule |
|
||||
| #ledger | kept, under BUILD last | criticism with a ledger id and a date, as announcement 1 says |
|
||||
| #discord-updates | kept, hidden (Founder and Core only) | the CI red watcher and the feed's proof post use its webhook |
|
||||
| #mining, #devnet, #proving, #start-here, #announcements, #rules | kept, topics and pins set below | |
|
||||
|
||||
### START HERE (read-only for @everyone: Send Messages off on the category; Core and Founder post)
|
||||
|
||||
| Channel | Topic | Pinned first post | Settings | Set / read back |
|
||||
|---|---|---|---|---|
|
||||
| #start-here | What Igneum is, what to do first, the ladder. | the welcome post (section 3.3) | read-only; the server guide's first to-do | set, read back |
|
||||
| #rules | Three rules. Read once. | the rules post (section 2.6, the same words as the rules screen) | read-only | set, read back |
|
||||
| #announcements | Releases and chain events only. Announcement channel (followable). | announcement 1 is already here (announcement-01.md); edit its "How this channel works" line to: "Releases in #releases, numbers every hour in #network-feed from the labelled bot. Incidents, with cause and fix, in #incidents. Questions in #mining or the support channel for your OS, criticism in #ledger, where it gets a ledger id and a date." | read-only; Announcement channel type | set, read back |
|
||||
| #releases | One post per release: version, downloads, sha256, what changed. The app updates itself. (The "Igneum" release webhook was moved here from #announcements at 13:13 UK; read back from Discord: channel 1557346271726141471.) | "Every release lands here from the bot: the version, the three downloads with their sha256, the node commit, up to five lines on what changed. The app updates itself over the air at a safe moment; a fresh install is at igneum.network/miner. A release is posted after it has crossed the staging chain, never before." | read-only; `DISCORD_WEBHOOK_ANNOUNCEMENTS` moves here (Channel settings, Integrations, Webhooks, edit the channel of the "Igneum" webhook); #announcements keeps human posts only | set, read back |
|
||||
| #incidents | What broke, who it affects, what is being done. Then the cause and the fix. | "An incident is posted when it opens and when it resolves: the UTC time, what happened, who is affected per tier, what is being done; then the cause, the fix and how long it lasted. Three are automatic from the public API: finality paused over five minutes, proving over fifteen minutes behind, the live numbers stale over three minutes. Everything else a person wrote." | read-only | set, read back |
|
||||
|
||||
### MINE
|
||||
|
||||
| Channel | Topic | Pinned first post | Settings | Set / read back |
|
||||
|---|---|---|---|---|
|
||||
| #mining | Mining talk: cards, rates, settings, what your card is doing. | "Mining questions go here. Say the card, the OS and the app version; the Cards page shows all three. Every rate the project publishes has a row in the bench table (igneum.network/miners) with the command and the hardware. Nothing here is estimated earnings. No price talk: there is no market and nothing is for sale." | slowmode 10 s; threads on | set, read back |
|
||||
| #rig-photos | Your rig, your card, your first block card. Photos only, a line of text. | "Post the rig. One photo or the saved block card, one line: the cards, the OS, the rate the app shows. No serial numbers, no machine names, no payout addresses in the shot. Threads for the replies, so the photos stay a wall." | threads on; slowmode 10 s; Attach Files on for @everyone in this channel only | set, read back |
|
||||
| #first-block | Every first block, posted by the bot. Your reply under it. | "When a key finds its first blue block the bot posts it here: the short key id and the block link, and the card if the miner switched 'Make my page public' on in the app. Reply in the thread under it. The next rungs are in #start-here." | threads on; Send Messages off for @everyone, Send Messages in Threads on; the ladder bot posts (section 4.2) | set, read back |
|
||||
| #benchmarks | Your card's rate and watts, the command, the app version. The bench table is built from rows like these. | "A benchmark is a row: card, driver, OS, app version, MH/s, W, and the command or the screen it came from. The project's own rows are at igneum.network/miners with the hardware named. Say the watts; a row without them is not used." | slowmode 10 s; threads on | set, read back |
|
||||
| #support-windows | Windows: install, SmartScreen, the firewall prompt, drivers. | the FAQ post (section 1.5) with the Windows line first: "SmartScreen shows 'Windows protected your PC' on a new build: More info, then Run anyway. The firewall prompt comes 20 to 50 seconds in; it is the app opening its port." | slowmode 10 s; threads on by default | set, read back |
|
||||
| #support-mac | macOS: Gatekeeper, Open Anyway, Apple silicon. | the FAQ post with the Mac line first: "macOS refuses an app it has not seen: System Settings, Privacy and Security, Open Anyway. Apple silicon mines; it proves on the CPU, slowly." | slowmode 10 s; threads on by default | set, read back |
|
||||
| #support-linux-hiveos | Linux and HiveOS: the tarball, the flight sheet, drivers. | the FAQ post with the Linux line first: "The Linux and HiveOS tarball and the flight sheet are at igneum.network/miner. Say the distribution and the driver version with every question." | slowmode 10 s; threads on by default | set, read back |
|
||||
| #pools | Pools: the project's pool-0 when it opens, and every outside pool with a signed statement. | "Until the public testnet every machine mines solo on its own keys, and a card runs several. Pools come with the testnet; the project runs pool-0 at 1 percent, the same as the optional software fee, and an outside pool is listed here once it publishes a signed key list. A pool never holds your vote weight: the member's own vote key is in every header." | slowmode 10 s; threads on | set, read back |
|
||||
|
||||
### #network-feed (its own row under MINE; the bot's only voice)
|
||||
|
||||
| Channel | Topic | Pinned first post | Settings | Set / read back |
|
||||
|---|---|---|---|---|
|
||||
| #network-feed | Numbers from the public API, every hour. Read-only. | "Every hour: height, hash rate, keys and the last lock, from igneum.network/api/live. Four times a day the fuller pulse, at 09:00 UK the daily digest, Monday the weekly numbers, daily the hash-origin report (who found the blocks). Milestones once each. The bot never replies; questions go to #mining." | Send Messages off for @everyone and every role; webhooks "Igneum" (numbers) and "Igneum feed" (feed) | set, read back; two feed posts in the channel at 13:05 and 13:06 UK |
|
||||
|
||||
### BUILD
|
||||
|
||||
| Channel | Topic | Pinned first post | Settings | Set / read back |
|
||||
|---|---|---|---|---|
|
||||
| #devnet | The devnet: resets, activations, what the node is doing. | "The devnet has mined since 3 October 2026. Coins on it have no value and the chain may be reset. A consensus change flips when 95 percent of mining weight signals it, with a floor height as the backstop; it is announced here first. The staging chain crosses first, every time." | slowmode 10 s; threads on | set, read back |
|
||||
| #proving | Proving: shards, the proof pool, what your card can prove. | "Every block is proven in shards by miners' cards and paid from the block. The Prove page of the app says in one sentence what your card can do: the full shard needs a 24 GB NVIDIA card, a 32 GB card mines and proves at once, Apple silicon proves on the CPU. Proof lag and shards paid are in #network-feed." | slowmode 10 s; threads on | set, read back |
|
||||
| #node-runners | Running a node: sync, peers, the RPC, the snapshot. | "A node runner's channel. Say the version, the height and the peer count; the node prints all three. A node that pauses finality reports it itself. Snapshots are refused below the node's tip; a reorg never resets execution. Node problems that look like consensus go to #devnet." | slowmode 10 s; threads on; the pre-join "I run a node" lands here | set, read back |
|
||||
| #dev | Building on or with Igneum: the client, the API, the explorer, tools. | "For people writing code. The public API is igneum.network/api/stats, /api/live and /api/supply, every field named. The repository opens with the public testnet. A claim about another chain cites the repo and file or is labelled approximate." | slowmode 10 s; threads on; the pre-join "I build" lands here | set, read back |
|
||||
| #spec | The spec, line by line. An issue on the spec is the way to report a flaw. | "The litepaper is at igneum.network/litepaper and the spec pages under it. Quote the line you mean. A flaw goes to hello@igneum.network or an issue on the spec; it gets a ledger id and a date in #ledger." | slowmode 10 s; threads on | set, read back |
|
||||
| #ledger | Every criticism, with a ledger id and a date, and what was done. | "Criticism lands here and at igneum.network/ledger with an id and a date. Nothing is deleted; a resolved item says how. The founder is one person, pseudonymous, building with AI systems; say so if that is your criticism, it has an entry." | slowmode 10 s; threads on | set, read back |
|
||||
|
||||
### COMMUNITY
|
||||
|
||||
| Channel | Topic | Pinned first post | Settings | Set / read back |
|
||||
|---|---|---|---|---|
|
||||
| #general | Everything else about Igneum. | "General talk. The three rules: be kind, no seed phrases ever, nobody from Igneum will DM you first. No price talk: there is no market and nothing is for sale. Regional threads open here when a language has ten people asking for one." | slowmode 10 s; threads on | set, read back |
|
||||
| #off-topic | Not Igneum. GPUs, games, the weather. | "Anything but Igneum and anything but prices. Same three rules." | slowmode 10 s | set, read back |
|
||||
|
||||
### Hidden (Founder and Core)
|
||||
|
||||
| Channel | Topic | Settings | Set / read back |
|
||||
|---|---|---|---|
|
||||
| #mod-log | AutoMod alerts and moderation notes. | private; every AutoMod rule's alert channel | set, read back |
|
||||
| #discord-updates | CI red runs, the feed's proof posts, tooling notes. | private; unchanged; holds the `DISCORD_WEBHOOK_UPDATES` webhook | exists |
|
||||
|
||||
Regional threads: not now. A regional thread opens in #general when ten members ask for one language; it is a thread,
|
||||
not a channel, until it carries a week of talk (the no-empty-channels rule applies to channels, not threads).
|
||||
|
||||
### 1.5 The support FAQ pin (the same post in the three support channels, the OS line first)
|
||||
|
||||
From the miner page's "Before you start" section, verbatim where it is quoted:
|
||||
|
||||
```
|
||||
Before you start. The questions worth asking.
|
||||
|
||||
Does this website use my GPU to mine?
|
||||
No. Mining happens only in the app you install, and only when you press Start.
|
||||
|
||||
Does my card mine and prove?
|
||||
Every card mines. Proving the full shard needs a 24 GB NVIDIA card; a 32 GB card does both at once. Apple silicon proves on the CPU, slowly. The Prove page of the app says in one sentence what your card can do.
|
||||
|
||||
Is the devnet paying real money?
|
||||
No. Devnet coins have no value and the chain may reset. The app's pounds row reads 0.00 on devnet and says why.
|
||||
|
||||
Is there a fee?
|
||||
Not in the protocol: no dev fund, no fee to any team. The app takes an optional 1% software fee, the norm for GPU miners, and one flag turns it off.
|
||||
|
||||
Can I run it on a rig or in a pool?
|
||||
The Linux and HiveOS tarball is on the miner page with the flight sheet. Pools are part of the public testnet; until then every machine mines solo on its own keys, and a card runs several.
|
||||
|
||||
Where do the numbers come from?
|
||||
Every rate has a row in the bench table and an entry in the engineering log with the command and the hardware. Nothing is estimated earnings.
|
||||
|
||||
Ask here with the card, the OS and the app version. Nobody from Igneum will DM you first. igneum.network/miner
|
||||
```
|
||||
|
||||
## 2. Moderation
|
||||
|
||||
| Setting | Value | Where | Set / read back |
|
||||
|---|---|---|---|
|
||||
| Verification level | Medium (was already Medium) | Safety Setup, DM and Spam Protection | read back |
|
||||
| Explicit image filter | Filter messages from all members (was already on) | Safety Setup, AutoMod, Sensitive content filters | read back |
|
||||
| 2FA requirement for moderation | off: the switch is greyed until the founder account enables 2FA | Server Settings, Safety Setup, Permissions | OWED (needs the founder) |
|
||||
| @everyone role | Mention @everyone, @here and All Roles: off. Also off: Manage Messages, Manage Threads, Create Private Threads, Use External Emoji stays on, Create Invite on | Server Settings, Roles, Default Permissions | set, read back |
|
||||
| Bot role | View Channels, Send Messages, Embed Links, Attach Files, Read Message History. Nothing else (no Manage, no Mention Everyone, no Administrator). Webhooks are not members and carry no role; this role is for the ladder bot's user only until that bot gets its own role (section 4.2) | Server Settings, Roles, Bot | set, read back |
|
||||
| #mod-log | private channel, Founder and Core; the alert channel of every AutoMod rule | Channel create | set, read back |
|
||||
| Rules screen | Server Rules on (Access tab), four lines: the three rules and the no-price plus report-a-flaw line | Server Settings, Access, Server Rules | set, read back |
|
||||
| DM spam | DM and Spam Protection 5 of 5 on; Raid Protection and CAPTCHA 3 of 3 on; activity alerts to #discord-updates; member prune restricted to admins (set today) | Safety Setup | read back |
|
||||
|
||||
### 2.1 to 2.5 AutoMod rules, as built (Server Settings, Safety Setup, AutoMod; every alert to #mod-log; Core exempt; #mod-log and #discord-updates exempt where a channel field exists)
|
||||
|
||||
Discord's keyword rule takes words and wildcards; the regex field was not needed. Seven rules are live:
|
||||
|
||||
| # | Rule (Discord name) | Trigger | Action | Read back |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Invite and spam links (14 words) | `*discord.com/invite/*, *discord.gg/*, *discordapp.com/invite/*, *discord.com/invite*, *bit.ly/*, *tinyurl.com/*, *cutt.ly/*, *t.me/*, *wa.me/*, *.xyz/*, *.top/*, *.click/*, *.buzz/*, *.icu/*` (the old "Igneum words and invites" rule, renamed and rewritten; its price words moved to rule 3) | block, alert #mod-log | enabled |
|
||||
| 2 | Seed phrases and wallet scams (19 words) | `seed phrase, seed phrases, recovery phrase, secret phrase, 12 words, 24 words, private key, send to, send me, dm me, message me first, validate your wallet, sync your wallet, connect your wallet, claim your, airdrop, giveaway, support ticket, open a ticket` | block, alert, time out 10 min | enabled |
|
||||
| 3 | Price pumping (alert only) (22 words) | `100x, 1000x, to the moon, wen moon, pump, pumping, buy now, buy the dip, price prediction, price target, listing soon, when binance, wen binance, wen lambo, presale, ico, ido, wen listing, wtb, wts, for sale, otc` | alert only, no block (a miner asking is a conversation; a mod answers with the pin) | enabled |
|
||||
| 4 | Impersonation of the team (10 words) | `igneum team, igneum support, official igneum, igneum admin, igneum mod, igneum staff, from igneum, igneum official, igneum helpdesk, igneum customer service` | block, alert | enabled |
|
||||
| 5 | Block Words in Member Profile Names | `*igneum*, *admin*, *moderator*, *support*, *official*, *helpdesk*, *team*` in display names | block member interactions, alert | enabled |
|
||||
| 6 | Block Commonly Flagged Words (Discord preset) | Severe Profanity, Insults and Slurs, Sexual Content, all three lists | block, alert | enabled |
|
||||
| 7 | Block Suspected Spam Content (Discord preset) | Discord's spam model | block, alert | enabled |
|
||||
| 8 | Block Mention Spam (Discord preset) | was already on | block | enabled |
|
||||
|
||||
Owed: the test from a non-Core account (one blocked message per rule in #mod-log, one legitimate message passing: a payout address, a sha256, "2x per joule"). The founder's account is Core-exempt and the server has one member, so the test waits for a second account.
|
||||
|
||||
### 2.6 The rules text (the rules screen and #rules, the same words)
|
||||
|
||||
```
|
||||
Three rules.
|
||||
|
||||
1. Be kind. Argue the claim.
|
||||
2. No seed phrases ever. Not yours, not anyone's, not in a DM, not "to check".
|
||||
3. Nobody from Igneum will DM you first. Anyone who does is not from Igneum.
|
||||
|
||||
No price talk: there is no market and nothing is for sale.
|
||||
|
||||
Report a flaw: hello@igneum.network or an issue on the spec. Nobody from Igneum will ask for your seed.
|
||||
```
|
||||
|
||||
## 3. Onboarding
|
||||
|
||||
### 3.1 Server guide to-dos (Server Settings, Onboarding, Server Guide), aligned with the map
|
||||
|
||||
| # | To-do | Channel |
|
||||
|---|---|---|
|
||||
| 1 | Read what Igneum is and the ladder | #start-here |
|
||||
| 2 | Read the three rules | #rules |
|
||||
| 3 | Pick your lane: Miner, Node runner, Builder (the question you answered on joining; change it in Channels and Roles) | #mining |
|
||||
| 4 | Post your first block when it lands | #first-block |
|
||||
| 5 | Follow releases | #releases |
|
||||
|
||||
As built: to-do 1 "Read what Igneum is and the ladder" in #start-here (visit), 2 "Say hello in mining: your card, the OS, the app version" in #mining (message), 3 "Post your first block when it lands" in #first-block (visit), 4 "Follow releases: version, downloads, what changed" in #releases (visit), 5 "Read the rules" (Discord's built-in). Welcome sign: "Welcome. Three rules in #rules: be kind, no seed phrases ever, nobody from Igneum will DM you first. #start-here has the ladder: your first block lands in #first-block. Nothing is for sale and devnet coins have no value." Author igneum_network. The guide banner is the one in README.md.
|
||||
|
||||
### 3.2 The pre-join question (Server Settings, Onboarding, Default Channels and Questions)
|
||||
|
||||
Question "What do you mine with?" stays. Answers and what each hands out:
|
||||
|
||||
| Answer | Role | Channels joined |
|
||||
|---|---|---|
|
||||
| NVIDIA | Miner | #mining, #first-block, #rig-photos, #support-windows, #support-linux-hiveos |
|
||||
| AMD | Miner | #mining, #first-block, #rig-photos, #support-windows, #support-linux-hiveos |
|
||||
| Apple | Miner | #mining, #first-block, #rig-photos, #support-mac |
|
||||
| I run a node | Node runner | #node-runners, #devnet |
|
||||
| I build | Builder | #dev, #spec, #proving |
|
||||
|
||||
Multiple answers allowed, not required. As built (13:0x UK): the five answers re-pointed exactly as the table above; Discord reports 22 of 22 public channels assignable and no public channel missing from Questions and Default Channels (22 default channels, unchanged).
|
||||
|
||||
### 3.3 The welcome post in #start-here (pinned, from the founder's account)
|
||||
|
||||
```
|
||||
Welcome to Igneum.
|
||||
|
||||
A proof-of-work chain built for graphics cards. The miners also prove every block and are paid for it from the block. No premine, no stake, no fee to any team in the protocol. One founder, pseudonymous, building with AI systems; every criticism we know of is at igneum.network/ledger.
|
||||
|
||||
Start here
|
||||
1. Install the miner: igneum.network/miner. Windows, macOS, Linux and HiveOS.
|
||||
2. Press Start. The app says what your card can do.
|
||||
3. Your first block lands in #first-block. Reply under it.
|
||||
|
||||
The ladder. The chain computes it, nobody hands it out.
|
||||
First block: one blue block with your key. The bot posts it in #first-block.
|
||||
Your key has a vote: 100 blocks in the 30-day window (the dust line). The Voter role.
|
||||
Your signature is in a checkpoint: the first lock carrying your vote.
|
||||
Full window: 30 of 30 days with blocks. The Window role.
|
||||
Rank: your place by weight among all keys, on igneum.network/live.
|
||||
Your card proved a shard: a paid shard record. The Prover role.
|
||||
|
||||
Questions in #mining or the support channel for your OS. Numbers every hour in #network-feed. Rules in #rules; there are three.
|
||||
Devnet coins have no value and the chain may be reset. Nothing is for sale.
|
||||
```
|
||||
|
||||
The rung names and rules are reinvent.md section 3.7 and ladder.md; on the devnet the dust line is the chain's
|
||||
`params.dust` (5 today), and the bot's rung 1 post says the number it read, never the constant.
|
||||
|
||||
## 4. Bots
|
||||
|
||||
Two bots. Neither replies, neither mentions, both post from the public API only, both go through the forbidden-string
|
||||
guard in `tools/community/discord-hooks.mjs` (the founder's name, hosts, machine ids, paths, IPs, 32-hex tokens,
|
||||
webhook URLs, any mention).
|
||||
|
||||
### 4.1 The network feed bot (webhook; BUILT and LIVE from the box since 13:06 UK, 7 October 2026)
|
||||
|
||||
What it is: `tools/community/discord-hooks.mjs feed`, added 7 October 2026 beside pulse, digest and weekly. A webhook
|
||||
needs no bot user, no token and no Discord permission beyond the webhook itself (Channel settings, Integrations,
|
||||
Webhooks, "Igneum feed" on #network-feed, or the existing "Igneum" webhook's URL copied into the key).
|
||||
|
||||
| Post | When | Exact shape |
|
||||
|---|---|---|
|
||||
| Network feed | every hour, on the London clock, through `tick` | title "Network feed" linking igneum.network/live; line 1 "igneum-devnet. Devnet coins have no value. https://igneum.network/live"; fields Height (`state.height`, blocks/s over the hour from `block_count`), Hash rate (`state.hashes_per_second_estimate`, difficulty), Keys (`state.miners_10m` "active in 10 min (a card runs several)", `finality.weights.voters` "voters in the 30-day window"), Last lock (the newest locked checkpoint: index, share of weight, age, voters); footer the UK time with UTC in brackets and "every hour from /api/live; this channel is read-only, questions go to #mining" |
|
||||
| Hash origin, daily | in the first feed of the day that sees a new `state.hash_origin.date` (the launch pack's job writes it at 08:30 UTC) | one extra field "Hash origin, <date>": "N keys found a block, M above dust, ten largest X% of blocks, project fleet Y%, P attested pools. Full report in #numbers." A field the report lacks is left out, never estimated |
|
||||
| Milestone | once each, when a feed crosses it | title "Milestone": every 100,000 chain blocks ("Chain block 100,000. igneum.network/block/100000"), every 10,000 locks, every 10,000 paid shards, the first crossing of 100, 250, 500, 1,000, 2,500, 5,000, 10,000 vote keys active at once, the first crossing of 1, 10, 100 GH/s and 1, 10, 100 TH/s. The first feed seeds and posts none |
|
||||
|
||||
Read back today (7 October 2026, 11:30 UK, dry run against the live API, then one live post through the updates
|
||||
webhook as the proof of the path): "Height 162,099, 1.47 blocks/s last 60 s; Hash rate 444.7 MH/s, difficulty
|
||||
210,368,365; Keys 17 active in 10 min, 16 voters in the 30-day window; Last lock checkpoint 8,723 at 72.1% of weight,
|
||||
16 s ago, 16 voters". 413 characters. Tests: 37 pass (`node --test tools/community/discord-hooks.test.mjs`).
|
||||
|
||||
What the numbers mean per tier (the consequences rule): 17 keys and 444.7 MH/s is the project's own machines on the
|
||||
devnet, so the feed names no outside miner yet; a home miner with one 8 GB card at the measured rates sees a first
|
||||
block inside the hour at this size, and the Keys line is the one that tells them when that stops being true (the
|
||||
pool is the answer from about 1 TH/s, reinvent.md 3.5).
|
||||
|
||||
Turned on 7 October 2026, 13:05 UK: the webhook "Igneum feed" was created on #network-feed (Channel settings,
|
||||
Integrations, Webhooks), its URL copied straight from Discord's button into `~/.config/igneum/discord` as
|
||||
`DISCORD_WEBHOOK_FEED` (the clipboard was cleared after; the URL was never printed), then
|
||||
`IGNEUM_SECRET_ON_BOX_OK=1 infra/build-server/discord-hooks/install.sh` copied the new script and the file to the box.
|
||||
The box's tick posted `feed:2026-10-07:13` at 13:06 UK (height 163,299, 310.5 MH/s, 14 keys active, 12 voters, lock
|
||||
8,910 at 80.3 percent) and the next tick read "0 due". Two webhooks now post to #network-feed: "Igneum" (numbers:
|
||||
pulse, digest, weekly, hash-origin) and "Igneum feed" (the hour and milestones).
|
||||
|
||||
What the numbers mean per tier (the consequences rule): 14 keys and 311 MH/s is the project's own machines on the
|
||||
devnet, so the feed names no outside miner yet; a home miner with one 8 GB card at the measured untuned rates finds a
|
||||
first block inside the hour at this size, and the Keys line is the one that tells them when that stops being true
|
||||
(the pool is the answer from about 1 TH/s, reinvent.md 3.5).
|
||||
|
||||
### 4.2 The ladder bot (a bot user; SPECIFIED, not built)
|
||||
|
||||
What it posts, from ladder.md (the shapes are fixed there; the words are repeated here so this file stands alone):
|
||||
|
||||
| Post | Channel | Trigger | Exact shape |
|
||||
|---|---|---|---|
|
||||
| First block (rung 0) | #first-block | a key's `blocks` goes 0 to 1 in `finality.weights.keys[]` and a block in `/api/live blocks[]` names it | "First block: key 6ad0e117" / "Block 1,284,117 · igneum.network/block/<hash>" / "RTX 4070 (the miner chose to show the card)". Without the opt-in the third line is absent. Embed: ember, title "First block", fields Key and Block, UK footer |
|
||||
| The vote (rung 1) | #first-block | `keys[].voter` goes false to true | "Your key has a vote: 6ad0e117" / "100 blocks in the window (the line is 100) · the key now signs every checkpoint"; the number is `params.dust` as read |
|
||||
| The ladder, yesterday | #network-feed | 09:00 UK beside the digest | the seven-line summary in ladder.md (first blocks, votes, signatures, full window, rank 1, signing streak, shards); a line whose field the node does not expose is left out |
|
||||
| Roles | | daily read of `getFinalityWeights` through `/api/live` | Voter granted when a message signed with the vote key names a key whose `voter` is true; revoked at a daily read where `voter` is false. Window when `keys[].daysMined` spans the window (owed from the node; by hand until then). Prover when a paid shard record names the key; never revoked |
|
||||
|
||||
Opt-in, per miner: the key id and the block link post for every key (a chain fact); the card model and the "(you)"
|
||||
link only when the miner switched "Make my page public" on in the app (`settings.profile_public`). The Discord role
|
||||
needs the miner to prove the key: a `/key <id> <signature>` slash command, the signature over a nonce the bot gives,
|
||||
verified against `getFinalityWeights`; the app's "prove my key" button is owed (ladder.md).
|
||||
|
||||
Permissions the bot user needs, minimal: View Channels; Send Messages and Embed Links in #first-block and #network-feed
|
||||
only (channel overrides, not server-wide); Read Message History; Manage Roles, with the bot's role placed above Voter,
|
||||
Window and Prover and below everything else; `applications.commands` scope for the slash command. Not: Administrator,
|
||||
Mention Everyone, Manage Channels, Manage Webhooks, Moderate Members.
|
||||
|
||||
Rungs 2 to 5 and P are never posted singly; they are counted in the daily summary (one a minute at scale).
|
||||
|
||||
### 4.3 The two webhooks that exist and where they point
|
||||
|
||||
| Key | Channel | Used by |
|
||||
|---|---|---|
|
||||
| DISCORD_WEBHOOK_NUMBERS | #network-feed (the channel was renamed; the webhook followed) | pulse, digest, weekly, the hash-origin report |
|
||||
| DISCORD_WEBHOOK_ANNOUNCEMENTS | #releases (moved 7 Oct 2026, 13:13 UK; the webhook keeps its URL and name "Igneum") | release posts |
|
||||
| DISCORD_WEBHOOK_INCIDENTS | #incidents | incident open and resolve, the watcher |
|
||||
| DISCORD_WEBHOOK_UPDATES | #discord-updates (hidden) | the CI red watcher; the feed's proof post today (`feed --via updates`) |
|
||||
| DISCORD_WEBHOOK_FEED | #network-feed, webhook "Igneum feed" (created 13:0x UK) | the hourly feed and milestones |
|
||||
|
||||
## 5. Events
|
||||
|
||||
One template, "Devnet office hour", weekly. Discord cannot hold an event without a start time, so nothing is created on the server until the founder picks a day and an hour; the fields below are the template, created in one minute on that day.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Name | Devnet office hour |
|
||||
| Where | a voice channel "office-hour" under COMMUNITY, created with the first event (not before: no empty channels) |
|
||||
| Description | "An hour on the devnet, every week. Bring the card, the log line or the question. Numbers from the public API only; nothing is for sale and there is no price." |
|
||||
| Recurrence | weekly, same day and hour, UK time |
|
||||
| Needs the founder | the day and the hour |
|
||||
|
||||
## 6. What needs the founder
|
||||
|
||||
| Item | Why |
|
||||
|---|---|
|
||||
| A day and an hour for the office hour | section 5; Discord has no undated event |
|
||||
| 2FA on the founder account, then the "Require 2FA for moderator actions" switch | Discord greys the switch until the account has 2FA; one click after that |
|
||||
| The hosting word for the ladder bot | a bot token with Manage Roles is a secret the box would hold; the 6 October exception covers webhook URLs only. Options: the box under a second exception, or a separate small host that holds only the bot token and reads the public API |
|
||||
| A second account for the AutoMod test | the founder's account is Core-exempt; the seven rules are enabled but untested against a real message |
|
||||
|
||||
## Changelog
|
||||
|
||||
| When (UK) | What |
|
||||
|---|---|
|
||||
| 7 Oct 2026, 11:1x | Sheet opened on branch discord-structure. Chrome profile confirmed as the igneum.network login. Discord session found expired; stopped at the log-in form, nothing typed |
|
||||
| 7 Oct 2026, 11:2x | `feed` subcommand, milestones, the daily hash-origin field, `--via updates`, `DISCORD_WEBHOOK_FEED` (optional) and the tick wiring added to tools/community/discord-hooks.mjs; six tests added, 37 pass |
|
||||
| 7 Oct 2026, 11:30 | One live feed posted through the updates webhook: key `feed:2026-10-07:11`, message id 1557339585166581846, 413 characters |
|
||||
| 7 Oct 2026, 12:5x | Founder signed in. Categories renamed INFO, CHAIN, LEDGER, TALK to START HERE, MINE, BUILD, COMMUNITY. #ask read (no messages beyond the welcome; the r/igneum FAQ line is in the support pins) and renamed to #off-topic; #finality read (no messages) and renamed to #first-block; #proving read (no messages) and renamed to #rig-photos; #devnet (no messages) renamed to #benchmarks; #breaks (no messages) renamed to #spec; #numbers renamed to #network-feed (both webhooks follow). Created #releases, #mod-log (private, Core), #support-windows, #support-mac, #support-linux-hiveos, #pools, #devnet, #proving, #node-runners; #dev moved to BUILD. Every topic and slowmode set; #first-block Send Messages denied, threads allowed; #network-feed Send Messages denied. The 0.3.15 and 0.3.16 posts and the two incidents stay where they were |
|
||||
| 7 Oct 2026, 12:2x to 12:3x | 19 first posts sent from the founder's account and pinned: start-here (the welcome and ladder), releases, incidents, network-feed, mining, rig-photos, first-block, benchmarks, support-windows, support-mac, support-linux-hiveos (the FAQ, OS line first), pools, devnet, proving, node-runners, dev, spec, ledger, off-topic |
|
||||
| 7 Oct 2026, 12:4x | AutoMod: seven rules enabled (section 2), prune restricted to admins, safety notifications to #mod-log |
|
||||
| 7 Oct 2026, 12:5x | Onboarding: the five answers re-pointed, four to-dos rewritten, welcome sign rewritten; Server Rules screen on with four lines; Bot role set to View Channels, Send Messages, Embed Links, Attach Files, Read Message History (read back 13:1x UK: exactly those five on, 46 off) |
|
||||
| 7 Oct 2026, 13:05 | Webhook "Igneum feed" created on #network-feed, key written, box installed; 13:06 the box's first hourly feed |
|
||||
| 7 Oct 2026, 13:1x | Post 1's "How this channel works" line edited by webhook PATCH (message 1557154683779547138); the "Igneum" release webhook moved from #announcements to #releases (Channel settings, Integrations, Webhooks, Channel), read back from each webhook's own endpoint: announcements → 1557346271726141471 (#releases), numbers and feed → 1557089967807926322 (#network-feed) |
|
||||
|
|
@ -41,7 +41,7 @@ Versions in the table: `igneum-pow` is the Rust crate at `igneum-pow/Cargo.toml`
|
|||
| 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`. Second RTX 5090 run, 4 October 2026 evening (job run-20261004-173115): a full shard at the provisional S_p (6.75 M pgas, 60.8 M cycles) executed in 1.63 s, core proof 8.3 s (18.1 MB), compressed proof 10.9 s (1.27 MB, verify 0.040 s); a two-shard block (13.5 M pgas) proved shard by shard (11.7 s and 10.0 s) and aggregated in 2.2 s, 24 s of GPU stages end to end, every proof verified, six tampered witnesses rejected. The two host defects (an abort after the upload, an idle wait that turned out to be an unbuffered 18 MB proof save through the WSL2 file bridge, 24 minutes) are fixed (ledger P20) 5 October 2026, live devnet with real transactions (bench-log "real transactions, the first non-empty shard proven and paid"): block 72704 shard 0, 29 transfers, 5,800 pgas, proven on PC 2 in 34 s, verified on the Mac in 0.297 s and paid 1.7623 IGN, 53 s after the chain block executed; of about 1,400 blocks in the 20-minute window 36 were proven (the one prover takes the newest shard assigned to it), so "every block" is not yet true; a second content shard (72803, all copies skipped) failed the native-execution veto on the exporter's block structure, fixed with fixtures the same day, the node side pending the 0.3.9 rollout 5 October 2026, evening (bench-log "proving v1"): the aggregated segment record, the chain rule and the unproven rule are implemented behind `proving_v1_activation_daa` (branch proving-v1, not on the devnet before 0.3.11); on the RTX 5090 a chain of 8 consecutive live blocks proved and aggregated by recursion in 135.6 s with the miner on the card (17 s a block, one proof of 1,272,909 bytes attesting all 8, verified in 0.04 s); the 3-node fast-time harness paid a segment record 1.0 s after submission and refused a late one after its deadline (21 checks); the devnet itself, with one prover, carried proofs for 2.4% of blocks over 30 minutes at a block-to-record latency p50 44 s, p99 52 s. The "within about a minute" holds per proven block; "every block" needs 18 mining 5090s or 6 proving-only cards at empty blocks on the measured rates, and the mandatory rule stays off until the share is one | none yet |
|
||||
| 16 | A 12 GB card proves one shard in about 20 s (WITHDRAWN 5 October 2026: a 24 GB card proves a full shard at the adopted size in 4.3 s; 32 GB mines and proves) | 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" | Measured on a 32 GB card, not yet on a 12 GB card. A shard at the provisional `S_p` is 60.8 M SP1 cycles on the prototype pgas table (9 cycles per pgas, 44 per EVM gas; the modexp entry about 100x its SP1 cost); on an RTX 5090 (4 October 2026 evening, job run-20261004-173115) it executed in 1.63 s and its compressed proof took 10.9 s, verified in 0.040 s, so the 32 GB card is inside the 20 s target with margin. Whether a 12 GB card proves it at all, and in what time, is the next measurement (an RTX 3060 and an RTX 5060 Ti 16 GB are on order). A per-shard time can be met by shrinking the shard, so the project does not use it as a pass mark 5 October 2026, evening (bench-log "proving v1", the S_p curve): measured on the RTX 5090 with SP1 6.8.1's GPU prover, the card to itself, 1-s nvidia-smi samples: an empty shard 13,874 MiB and 2.2 s; a full shard at the ADOPTED v1 budget (30,000 pgas, 4.7 M cycles) 20,434 MiB and 4.3 s; the full prototype shard (6.75 M pgas, 60 M cycles) 28,307 MiB and 10.8 s; beside the miner 15,670 and 30,039 MiB. No environment knob of SP1 moves the 13.9 GB floor and the GPU server has no options of its own, so on this build a 12 GB card proves nothing, a 16 GB card only empty shards, a 24 GB card the adopted full shard alone and beside the miner (22,210 MiB and 13.2 s, measured on the 32 GB card: the 5090's allocation pattern, not yet a run on a 24 GB card) and a 32 GB card the prototype shard beside the miner with 2.5 GB spare. The litepaper line now says so; the 12 GB gate returns when a prover build with a smaller floor is measured on a 12 GB card | none yet |
|
||||
| 17 | The chip resistance claim: the strongest recompute chip under 1x per chip against an RTX 5090; the stored-dataset chip 1.2x per chip and 5x to 9x per joule in the model (2.1x to 4.8x by the Ethash precedent); the latency-shadow lever, measured and in its gates, brings it to about 2x | Homepage hero and litepaper abstract (draft (a) of `docs/plans/counter-asic-3-status.md` section 6, chosen 6 October 2026), litepaper "What Igneum does not claim" | tested by the team (the model), designed (the target) | program class v3 (Counter ASIC 2.0, 5 October 2026): branches ca2-v3 d233fa1 and after, ca2-mixer 1ab8b21, ca2-era 78c0ee4; `docs/analysis/chip-model-v3.md`, `docs/analysis/sram-mirror.md`, `docs/analysis/scratch-soundness.md` | The m16 recompute model re-run on the measured v3 rates and verifier times; the on-die-cache chip row | The on-die-cache recompute chip against the RTX 5090's measured 136.1 MH/s: class v2 2.4x; class v3 (mixer x8) 0.31x bare, 0.92x with a 3x fixed-function allowance (approximate), 0.76x at equal silicon; margin 8% on the allowance, 9% on the budget. 5 October 2026, M5 Max, RTX 5090, RX 9070 XT. The 2x target is a target: no chip has been built; the bounty stands (O-1.17) | none yet |
|
||||
| 17 | The chip resistance claim: the strongest chip in the public model reaches 5x to 9x per joule against an RTX 5090 today (modelled); class v4 brings it to 2.1x (k = 1) to 3.9x (k about 0.33, claimed by a withdrawn product) and its second rung to about 2.8x (modelled on measured watts); class v5 makes the dataset the chain's state so a stateless or stale chip is wrong on every item (designed, +0.2 ms verifier); the hot-set cache bounded at 1.067x at the ceiling (measured census of 1,024 programs) and the weak-day FPGA at most 12 percent on 12 days a century (measured census) are bounded and routed to the next class; datacentre silicon (H100 SXM, measured 7 October) does not change the question; a stored-dataset chip pays for itself only at about USD 100 M of market cap in two years (modelled) | The home page's chip line, the litepaper's chip model section, the miner page's line (the texts of `docs/plans/counter-asic-3-public-text-2026-10-07.md`) | tested by the team (every card, the verifier, the two attack-pass censuses, the H100), the chip itself modelled, class v5 and the ladder designed, the X9 core claimed and never measured | `docs/analysis/chip-model-v3.md` 5 and 6; `docs/analysis/latency-shadow-2026-10-06.md`; `docs/plans/counter-asic-3-status.md`; `docs/analysis/attack-pass/f8-uniform.md`, `f4-weakday.md`; `docs/design/class-v5-stored-state.md`; the H100 and market-cap rows of 7 October; `docs/plans/funding.md` (the three lots) | The chip model re-run on the measured class v3 and v4 rates, watts and verifier times; the hot-set census of 1,024 programs and the weak-day census of 2^24 days on the attack-pass branch; the H100 SXM bench row | 136 MH/s at 350 W (5090, bench) and 290 W (app); 27 MH/s at 21 W (M5 Max); 249 MH/s (H100 SXM) at 98 percent of its read ceiling, 1.78x hash, 1.15x MH/W, a third per rented dollar; 2.33 ms per warp; 5.1x to 9.2x; 2.1x, 3.9x, 2.8x; 1.067x at the ceiling; 12 percent on 12 days a century; 10.85 ms at rung 3; USD 100 M; 6 and 7 October 2026 | none yet; the three cryptanalysis lots are the next test |
|
||||
| 18 | The chip resistance measurements: the program is latency-bound (random reads), not bandwidth-bound, on every card we own, and sits beyond a card's on-chip cache | Litepaper Mining ("waits on memory latency, not on maths or bandwidth"), vs RandomX; the numbers page | tested by the team | readwidth e752fc7 (`docs/plans/read-width.md`), ca2-era 78c0ee4, ca2-cache 2de19e5 (`docs/plans/hot-table.md`) | The dependent-read probes at 32 to 1,024 MiB and the hash rate per class on the three cards; the latency-bound share = rate over the probe ceiling per load | Latency-bound share at the 1 GiB dataset: RTX 5090 0.96 (v2) and 1.01 (v3), RX 9070 XT 0.87 and 0.95, M5 Max 1.01 and 1.06; wider reads do not close the AMD gap (the 9070 XT does 2.4 G dependent reads per second at every width; the 5090 goes bandwidth-bound at 64 B, share 0.58); a 32 to 96 MiB hot table is not kept resident by any card while the dataset streams (g 0.80 to 0.87 in the added form). 5 October 2026 | none yet |
|
||||
| 19 | The lottery hash is sound as a hash: uniform output, deterministic, no out-of-bounds read, fuzzed; class v3 bit-exact on the three vendors | Litepaper vs RandomX ("Every number above is measured and logged"), the numbers page | tested by the team | ca2-mixer 1ab8b21 (`tests/mixer.rs`, `tests/scratch.rs`), ca2-era 78c0ee4, ca2-soundness a465881 (`docs/analysis/scratch-soundness.md`), `igneum-pow/tests/packs.rs` | The crate suite (53 + 4 + 19 + 7), the Metal fuzz, edge, stats and determinism runs on the v3 construction, the pack vectors and 2^24 fingerprints on Metal, Apple OpenCL, the RTX 5090 and the RX 9070 XT, the 1,024-hash CPU re-check per card | Class v3 (mixer x8 + era): 200-program fuzz 200 of 200 on Metal, every tenth on Apple OpenCL; the pinned v3 packs 3/3 + 3/3 and 96 of 96 lanes on Metal and Apple OpenCL; the six era packs' fingerprints equal on the three vendors (PC 1 job run-ca2-era-pc1-20261005, 5 October 2026); the v2 exports byte-identical on the v3 crate; the final-class PC rows and the G2 re-check: job run-ca2-era-pc1b-20261005 (pending at the time of writing) | 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 |
|
||||
|
|
|
|||
14
docs/evidence/reproduced/0.3.14.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Reproduced: Igneum Miner 0.3.14 (node 4c6b129d75c3d77a3689d22f1e1dc721b556aebb, app a90f6a5371ed19c62511255a4713eb36191848b9)
|
||||
|
||||
06 October 2026, 20:20 UTC on igneum-build-1 by `infra/build-server/repro/rebuild-on-box.sh` (driven by `tools/repro/rebuild-release.sh`): a clean clone of the fork at the node commit on branch `release-0.3.14-node` under a clean clone of the repo at the app commit, 2 independent clean passes per target in one target path each (no sccache, SOURCE_DATE_EPOCH 1791305478, TZ UTC), rustc 1.99.0, x86_64-w64-mingw32-gcc-posix (GCC) 13-posix, clang 18.1.3, glibc 2.39. Shipped hashes read from the public downloads (no token) where marked. Whole run 382 s; log `/srv/builds/_repro/0.3.14/rebuild.log`.
|
||||
|
||||
| Artefact | Shipped sha256 (source) | Box pass A | Box pass B | A vs shipped | A vs B | Reason for a DIFFER |
|
||||
|---|---|---|---|---|---|---|
|
||||
| igneumd | 934f393cacc31a06d0c45a9fe2e2f504941a32533110b51851968e70cf90fa3a (given) | 03f35e056922fa1e... (49600096 B) | 03f35e056922fa1e... (49600096 B) | **DIFFER** | **MATCH** | the shipped binary was not on hand this run (no public artefact), so only the hash is compared; the box's needs GLIBC_2.39, embeds /srv/builds/_repro/0.3.14, clock string 16:51:18; commit string 4c6b129d75c3d77a3689d22f1e1dc721b556aebb: 1 hit(s) in the box's binary |
|
||||
| igneum-miner | 7e296541 (given) | 900c1f0bf8a3b504... (9842168 B) | 900c1f0bf8a3b504... (9842168 B) | **DIFFER** | **MATCH** | the shipped binary was not on hand this run (no public artefact), so only the hash is compared; the box's needs GLIBC_2.39, embeds /srv/builds/_repro/0.3.14, clock string none |
|
||||
| igneumd.exe | 44fa74c02415ff258b5956ef55909dc97890e3f29fca3d2db26f7152ef4ce541 (given) | 166e604e01c668e6... (51758592 B) | 166e604e01c668e6... (51758592 B) | **DIFFER** | **MATCH** | box exe: Ubuntu GCC 13 posix, -Wl,--no-insert-timestamp (PE timestamp 'Jan 1 01:00:00 1970'), build path /srv/builds/_repro/0.3.14, clock string 16:51:18; the shipped exe itself is not on hand (innoextract 1.9 cannot read the Inno Setup 6 installer: setup loader revision 2; or no public installer for this version), so its hash is the given one and its header was not read; commit string 4c6b129d75c3d77a3689d22f1e1dc721b556aebb: 1 hit(s) in the box's binary |
|
||||
| igneum-miner.exe | 819ea9ce (given) | fefd266c3bd6470f... (10994688 B) | fefd266c3bd6470f... (10994688 B) | **DIFFER** | **MATCH** | box exe: Ubuntu GCC 13 posix, -Wl,--no-insert-timestamp (PE timestamp 'Jan 1 01:00:00 1970'), build path /srv/builds/_repro/0.3.14, clock string none; the shipped exe itself is not on hand (innoextract 1.9 cannot read the Inno Setup 6 installer: setup loader revision 2; or no public installer for this version), so its hash is the given one and its header was not read |
|
||||
|
||||
Full box hashes: igneumd A 03f35e056922fa1e406e14d35963e8d3ca57f98f0d58a04c3f7cc450a26d1fdc; igneum-miner A 900c1f0bf8a3b504dfb2a7fa7abb91f3286eb0d020ed1e0aa5f3ab808c0489eb; igneumd.exe A 166e604e01c668e69b88556cad959429c67b040ec1e024cf7e514e1b5fc8edae; igneum-miner.exe A fefd266c3bd6470f61476a96453d4ea0cb54c42b8b23038cf5ef5a3397399514;
|
||||
|
||||
Reading: MATCH against the shipped bytes is the goal; A vs B MATCH with a DIFFER against the shipped bytes means the box is deterministic and the shipped build came from another toolchain (the reason column says which facts differ); A vs B DIFFER is a non-determinism on the box itself and is the row to fix first.
|
||||
14
docs/evidence/reproduced/0.3.15.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Reproduced: Igneum Miner 0.3.15 (node 713ef876073d3661e9b48d2ead9a515afd1b2156, app 563485b769868ee34a530f1c40f7419109cc493f)
|
||||
|
||||
06 October 2026, 20:21 UTC on igneum-build-1 by `infra/build-server/repro/rebuild-on-box.sh` (driven by `tools/repro/rebuild-release.sh`): a clean clone of the fork at the node commit on branch `release-0.3.15-node` under a clean clone of the repo at the app commit, 2 independent clean passes per target in one target path each (no sccache, SOURCE_DATE_EPOCH 1791312828, TZ UTC), rustc 1.99.0, x86_64-w64-mingw32-gcc-posix (GCC) 13-posix, clang 18.1.3, glibc 2.39. Shipped hashes read from the public downloads (no token) where marked. Whole run 480 s; log `/srv/builds/_repro/0.3.15/rebuild.log`.
|
||||
|
||||
| Artefact | Shipped sha256 (source) | Box pass A | Box pass B | A vs shipped | A vs B | Reason for a DIFFER |
|
||||
|---|---|---|---|---|---|---|
|
||||
| igneumd | 1e51bfb6401e2d86022eeaddf03750a72d6eb200fa879b7c58fb5c3afbf38a50 (igneum-hive-0.3.15.tar.gz (1715e58ea1d4a1ed...)) | 1f1b6eee4aaf4cdf... (49720224 B) | 1f1b6eee4aaf4cdf... (49720224 B) | **DIFFER** | **MATCH** | toolchain: the shipped binary needs GLIBC_2.34, the box's GLIBC_2.39 (a glibc GLIBC_2.34 build is the Mac's infra/cross/build-linux.sh with zig; the box links the native clang/lld); build path in the shipped binary: /Users/joshm/.cargo/registry, in the box's: /srv/builds/_repro/0.3.15 (prost's protowire.rs embeds OUT_DIR, so a different path is a different binary); build clock string (mimalloc's __TIME__) in the shipped binary: 11:05:57, in the box's: 18:53:48 (with SOURCE_DATE_EPOCH the string is the commit's time of day, the same in every build; none = no mimalloc in the binary); commit string 713ef876073d3661e9b48d2ead9a515afd1b2156: 1 hit(s) in the box's binary, 1 in the shipped one (0 = the empty-commit class: a worktree build before the two-step clean) |
|
||||
| igneum-miner | c5b489105d933b01e4dceff8dc31e2db8e9aa53de06dddafc6eb12ee85b8f7a6 (igneum-hive-0.3.15.tar.gz (1715e58ea1d4a1ed...)) | a34e0a56c859a667... (9978968 B) | a34e0a56c859a667... (9978968 B) | **DIFFER** | **MATCH** | toolchain: the shipped binary needs GLIBC_2.34, the box's GLIBC_2.39 (a glibc GLIBC_2.34 build is the Mac's infra/cross/build-linux.sh with zig; the box links the native clang/lld); build path in the shipped binary: /Users/joshm/.cargo/registry, in the box's: /srv/builds/_repro/0.3.15 (prost's protowire.rs embeds OUT_DIR, so a different path is a different binary); build clock string (mimalloc's __TIME__) in the shipped binary: none, in the box's: none (with SOURCE_DATE_EPOCH the string is the commit's time of day, the same in every build; none = no mimalloc in the binary) |
|
||||
| igneumd.exe | none | 9b377455ac9cc3b9... (51847168 B) | 9b377455ac9cc3b9... (51847168 B) | NO SHIPPED HASH | **MATCH** | |
|
||||
| igneum-miner.exe | none | 65b30edd266d137f... (11129856 B) | 65b30edd266d137f... (11129856 B) | NO SHIPPED HASH | **MATCH** | |
|
||||
|
||||
Full box hashes: igneumd A 1f1b6eee4aaf4cdfec07a165e8242b6d69a9fe20eb1b73f3caf672ec3ff53f91; igneum-miner A a34e0a56c859a667200600d3bca32f9ae22e33deb98cd226e8f54e40a6e354a4; igneumd.exe A 9b377455ac9cc3b90f081bd12d954e400ad1549c158ac179ccf07d16b6bf2c8e; igneum-miner.exe A 65b30edd266d137f3320d4d477e83bf73e0795ac3c697cbcf46b05e7dba0f8a7;
|
||||
|
||||
Reading: MATCH against the shipped bytes is the goal; A vs B MATCH with a DIFFER against the shipped bytes means the box is deterministic and the shipped build came from another toolchain (the reason column says which facts differ); A vs B DIFFER is a non-determinism on the box itself and is the row to fix first.
|
||||
|
|
@ -164,6 +164,35 @@ Sweep (5 October 2026, evening): stated. `site/litepaper.html`, "For miners", Ha
|
|||
|
||||
---
|
||||
|
||||
### M32. "Automatic anti-ASIC escalators" overstates what the era draw and the instruction reserve do
|
||||
"You sell the era draw and the reserve unlock as anti-ASIC escalators, as if not knowing next era's parameters stops a chip. A chip that stores the dataset reads every drawn parameter as firmware: an address permute, a rotator, an immediate table. The families, the reserve order, the mixer, the dataset schedule and the class v4 shadow are all public at genesis. So what does the draw actually defend against?"
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon lane analysis `docs/analysis/horizon/algorithm.md` sections 5.4 and 8, lane 2): the era draw and the instruction reserve are automatic schedule changes against fixed datapaths and against human forks; against the stored-dataset chip every drawn parameter is firmware, and the defence against that chip is the latency-shadow work (class v4) and the price-per-joule model. Stated in `site/litepaper.html`, Mining section ("These are automatic schedule changes ... every drawn parameter is firmware") and the "A chip is impossible" item ("a chip wired for one program is a bad bet ... not the schedule"), the "Every six months" row of the comparison table, and `site/index.html`, the hourly-program note ("a chip wired for one program is useless"). The phrase "automatic anti-ASIC escalators" is withdrawn from public text; it stays in the internal design summary until that is next edited.
|
||||
|
||||
Answer: Correct. The draw hides (M, R, pos, the op weights within +-2, the fold rotations) until 2 hours before each era, and none of those needs silicon. Biasing the draw is priced at 20 days of 100 percent of the network's hash for one more sample of the same space (lane section 5.4), so the draw is unbiasable at any price that matters and that is its whole job: it is a fairness device and a fork-free schedule, not a chip defence. What a chip wired for one program loses to is the hourly program itself; what the stored-dataset chip loses to is the latency shadow (class v4, 2.1x per joule at k = 1 and 3.9x at k about 0.33, the X9’s core, against the 5090 bench row; 0.9x and 1.7x against the Apple M5 Max; recalibrated under X35) and the price per joule, which is where the public claim now rests.
|
||||
|
||||
Evidence: `docs/analysis/horizon/algorithm.md` sections 5.4 (the draw's randomness, the two routes priced) and 8 (the summary), 6 October 2026; the six-era hash-rate spread of 0.8 to 3.2 percent per card in bench-log "Counter ASIC 2.0, the numbers".
|
||||
|
||||
### M33. The FPGA ceiling rests on a tFAW the JEDEC HBM2 table does not give
|
||||
"Your chip model's HBM random-read ceiling takes 8 activates per 12 ns per channel from O'Connor and gets 10.7 G reads/s a stack; the epoch-length page's bank-bound row gets 11.4 and a 12.2 ceiling. JEDEC HBM2 tFAW is 28 ns with 4 activates per channel per window: 2.3 G. The one measured HBM2 FPGA random-read rate (Shuhai, FCCM 2020) is 2.4 G, right on the JEDEC ceiling. Your 1.9x FPGA ceiling is arithmetic on a timing the part does not have."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon lane analysis `docs/analysis/horizon/algorithm.md` section 5.1, the FPGA lane): the public FPGA line carries only the measured row, 2.4 G reads/s per card and 0.30x to 0.39x of the RTX 5090 per watt (Shuhai, FCCM 2020 Fig 7; the tFAW arithmetic from ICCAD 2021 Table I), and the 11.4 G bank-bound row and the 12.2 G ceiling are marked unmeasured until an AWS F2 hour measures them. Stated in `docs/analysis/chip-model-v3.md` section 5.3 (the activate-bound row marked UNMEASURED with the JEDEC figure beside it, and the FPGA paragraph after the table). The epoch-length analysis's 12.2 row is not on master yet and is corrected when it lands.
|
||||
|
||||
Answer: Correct. The measured 2.4 G/s had been read on 6 October as a mapping artefact ("the paper's point is that this mapping is the wrong one for random access"); the activate window says it is the DRAM's own limit, and a bank-interleaved mapping does not lift it because tFAW is enforced per channel by the die. The measurement that settles it is one AWS F2 hour (f2.6xlarge, Virtex UltraScale+ VU47P, 16 GB HBM2 in 2 stacks, 32 pseudo-channels, USD 1.98 an hour on demand): the chase kernel of `docs/benchmarks/repro.md` 2.2 ported to a Vitis HLS AXI master over the HBM IP at 1 GiB across all 32 pseudo-channels, 256 to 4,096 lanes in flight, board power at 1 Hz; pass line 15 to 25 M reads/s/W (0.3x to 0.5x of the 5090), alarm 27 (0.5x), over 54 (1.0x) a Counter ASIC 4.0 item. Consequence per tier: none today (no FPGA mines); on the measured row a soft-overlay FPGA mines at an RX 9070 XT's rate per watt for about 7x the price (approximate), so no home or rig tier is displaced.
|
||||
|
||||
Evidence: `docs/analysis/horizon/algorithm.md` section 5.1 (the ceiling table: measured 2.4, tFAW-bound 2.3, tRRD-bound 2.8, bank-bound 11.4, O'Connor 10.7 G reads/s, and the F2 measurement plan), 6 October 2026; JEDEC HBM2 timings as carried by ICCAD 2021 Table I; Shuhai, FCCM 2020, Fig 7.
|
||||
|
||||
### M34. The shadow size N is a constant of the binary, so the one lever against the dataset-storing chip needs a fork to move
|
||||
"Your own Horizon lane says the reserve and the era draw buy nothing against a chip that stores the dataset, and that the only lever is the latency-shadow size N. N is 27 passes of a 256-instruction block, hard-coded in `V4_CLASS`. So when HBM4 doubles a chip's rate per stack in 2028, your answer is a hard fork, and a fork that retires the M5 Max at the first doubling. And now there is a shipping RandomX ASIC."
|
||||
|
||||
Status: Relabelled (7 October 2026, morning, X36): the X9 in this row and in the litepaper paragraph is the chip Bitmain announced and withdrew before launch, its core claimed and never measured; the ladder's arithmetic against that core is unchanged.
|
||||
|
||||
Status: Conceded, implemented (6 October 2026, night; `docs/design/latency-ladder.md`, branch `ladder`, fork branch `ladder-node`, the 0.3.17 feature tree, behind `latency_ladder_activation_daa`, never until set, 0 on the testnet when the project lead says): N is a genesis ladder of six rungs (27, 35, 53, 88, 173, 267 passes; about 102,100 to 1,001,600 counted ops) with a measured admissibility flag per rung (cold verify under 10 ms on the reference core with its SMT sibling loaded, igneum-build-1, 6 October 2026: rungs 0 to 2 pass at 8.77, 8.87 and 9.23 ms, rung 3 misses by 0.08 ms under a box load of 25 and is out until a quiet re-run, rungs 4 and 5 are out at 12.38 and 14.96), and the step is consensus state derived from two bits of the header version: up one rung when 90 percent of blue blocks in each of seven consecutive windows ask for it and the rung above is admissible, down one rung symmetrically, never two rungs inside seven windows (the oldest window must begin after the last step took effect), never unconditionally. Tests, the known-failed case first: a changed N today hashes another program under the same program id (a hard fork no pack line told apart); after, rung 0 is class v4 byte for byte, a rung above carries its pass count in the id, 8,999 bps in one window of seven does not move the step, a two-step jump is impossible, down never passes rung 0, an inadmissible rung is never entered. Stated in `site/litepaper.html`, Mining section ("The work that waits can grow").
|
||||
|
||||
Answer: Correct on both counts, and the second was the sharper one. The X9 (Bitmain, about 1 MH/s at 2,472 W, approximate, github.com/monero-project/monero/issues/10270) is a shipped 3x per-joule edge over a desktop CPU on the best-known latency-bound random-program design, seven years after launch; it makes the k = 0.3 column of the chip model a product class rather than an attacker's claim, and the public headline is now the range 2.1x (k = 1) to 3.9x (k = 0.33) over the RTX 5090 at class v4, with the ladder taking the X9 bracket to about 2.8x by rung 2 and the Apple tier's to about 1.1x by rung 3. The ladder does not close the gap; it is the chain's only automatic answer, it moves at the pace of the cards that pay for it, and the honest card's watts remain the lever that moves every row (algorithm lane proposal 7). What the ladder gives up by design: a chip holding over 10 percent of weight can stall it, and the status quo it stalls is a rung the cards already run.
|
||||
|
||||
Evidence: `docs/design/latency-ladder.md` (the rule, the hostile review, the measured verifier table, the X9 arithmetic); `igneum-pow/src/generator.rs` test `latency_ladder_known_failed_a_changed_n_was_a_hard_fork_and_rungs_are_class_v4`; the fork's `consensus/core/src/igneum.rs` test `latency_ladder_rule`, `consensus/pow/src/igneum.rs` test `latency_ladder_rungs_are_programs_of_their_own_over_one_day_cache`; `infra/fast-time/latency-ladder.mjs` (the step, no-step and known-failed cases).
|
||||
|
||||
## 2. Finality and attacks
|
||||
|
||||
### F1. Finality is attackable for the first month
|
||||
|
|
@ -296,6 +325,15 @@ Evidence: design doc, "Proving speed on consumer GPUs" risk item and "Who needs"
|
|||
|
||||
---
|
||||
|
||||
### F26. "No stake" needs its one sentence: what is at stake, and what strips it
|
||||
"You write 'no stake' and then run a vote whose weight can be stripped. Either nothing is at stake, in which case equivocation costs nothing, or something is, in which case say what it is and who can take it. One sentence, in the finality section and the summary, not an argument."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon lane analysis `docs/analysis/horizon/frontier.md` section 4.1 and item I13 of its incremental list): `site/litepaper.html`, the finality section's "What is not here" paragraph and the "Igneum at a glance" Finality row carry the sentence verbatim: "No coin is staked. The only thing at stake is 30 days of public work: a vote key's weight is its blue blocks over the window, and equivocation strips it for 30 days." The spec sentence for 03 and 05 (I13) follows with the lane's commit.
|
||||
|
||||
Answer: Correct. The weight is a quantity that is at stake, earned by work alone over 30 days, not transferable, not purchasable, and already stripped in full for equivocation (spec 3.6). That is a slashable bond made of blocks, with no coin and no stake class; the "then it is stake" objection and its answer (the weight cannot be bought, borrowed or bridged, so capture by capital is removed; the last-days attack is bounded by the 30-day re-earn) are in the lane file, section 4.1. Extending the strip to execution-layer faults (the lane's 3.2) is an idea, not a rule, and the public text does not claim it.
|
||||
|
||||
Evidence: `docs/analysis/horizon/frontier.md` sections 3.2, 4.1 and the incremental list (I13), 6 October 2026; spec 3.6 (the equivocation strip).
|
||||
|
||||
## 3. Proving and the zkEVM
|
||||
|
||||
### P1. The 20-second shard is a number you made up
|
||||
|
|
@ -422,6 +460,24 @@ Sweep (5 October 2026, evening): stated. `site/litepaper.html`, Economics first
|
|||
|
||||
---
|
||||
|
||||
### P24. "20 percent of emission to provers" without the caveat that consensus does not verify the proof
|
||||
"Your 20 percent pays whoever submits a proof record whose statement matches the node's own execution. The node never checks the SP1 proof behind it. So the block producer, who writes the record, can claim shard pay with a false proof today, in proportion to its hash. Say so next to the 20 percent."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon security lane `docs/analysis/horizon/consensus-security.md` finding 3 and its proposal 1): `site/litepaper.html`, Economics, the 20% proving-pool row carries the caveat: consensus does not yet verify the carried proof, it checks the record's statement against native execution and its signature, so today a block producer could claim shard pay with a false proof (ledger P21; the in-consensus verifier is the 0.3.16 fix).
|
||||
|
||||
Answer: Correct; it is P21's finding read from the payer's side. The fix is the security lane's proposal 1: a record whose aggregated segment proof does not verify against the pinned aggregator key is invalid in consensus; the per-shard v0 record stays payout-only until then and is capped at the exclusive window. Consequence per tier: no honest miner loses anything today, since the pool is paid per valid record and the devnet's producers are the project's; the risk is a dishonest producer at the public testnet, which is why the fix lands in 0.3.16, before it.
|
||||
|
||||
Evidence: `docs/analysis/horizon/consensus-security.md` finding 3 and proposal 1, 6 October 2026; spec 07 7.7 item 4 and 7.8 item 8; ledger P21.
|
||||
|
||||
### P25. Unclaimed pool credit is stranded in the escrow
|
||||
"Spec 5.3 pays the first valid proof included in a block; 7.7 refuses a record older than 600 chain blocks; 7.8 says an unproven segment's aggregator share stays in the escrow. Nothing says what happens to the shard credit nobody claims. It sits there for ever. A ten-day refusal strands millions of IGN."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` section 4.2 and proposal 2): `site/litepaper.html`, Economics, the 20% proving-pool row carries the note: unclaimed pool credit is today stranded in the escrow, no rule returns it; the fix rolls an unproven shard's credit into the next proven segment's pool (0.3.16).
|
||||
|
||||
Answer: Correct. The lane's simulation puts a ten-day refusal at 5.5 M IGN stranded (section 4.2: the pool share paid falls to 0.67 with 0.33 stranded during the refusal, 182,387 IGN a day averaged); the devnet already burns the coinbase's 20% output at an unspendable script, so nothing is lost that was ever claimable today. The rule: an unproven shard's credit rolls forward into the next proven segment's pool instead of sitting in the escrow. Consequence per tier: a prover sees a larger pool after a refusal instead of a smaller one; nothing changes for a miner or a pool user.
|
||||
|
||||
Evidence: `docs/analysis/horizon/economy-and-utility.md` section 4.2 (the stranded share) and proposal 2, 6 October 2026; spec 5.3, 7.7 item 3, 7.8 item 7.
|
||||
|
||||
## 4. Economics and the coin
|
||||
|
||||
### E1. Hard cap plus burn is a security budget cliff
|
||||
|
|
@ -502,6 +558,33 @@ Evidence: litepaper "Liquidity from the people who are there".
|
|||
|
||||
---
|
||||
|
||||
### E19. "Proving: a second income" without the arithmetic of how small it is
|
||||
"You sell proving for other chains as the income that keeps cards on after the subsidy fades. Put a number on it. All of Ethereum L1's proving today costs tens of dollars a day. Your year-1 emission is tens of thousands a day at any price you dare print. Say which one pays the bills."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon lane analysis `docs/analysis/horizon/frontier.md` section 3.11, `frontier_model.py` section 7): `site/litepaper.html`, "For miners", under the three-streams table: all of Ethereum L1's proving is about USD 36 a day at the September 2026 tracker cost (USD 0.005 a block x 7,200 blocks; the tracker figure is a secondary source) against about USD 13,700 a day of Igneum's year-1 emission at USD 0.005 per IGN (31.688 IGN a block x 86,400; the price is an input, not a forecast), so external proving is a small second income at launch and the lottery pays the bills; paid demand would have to grow about 1,000x in dollars for proving to become the main income. Figures the lane labels approximate (all rollup proving spend, USD 8,200 to 27,400 a day; Boundless's trailing day, USD 2) are not on the page.
|
||||
|
||||
Answer: Correct. The whole public proving market is three to four orders of magnitude under year-1 emission at any price input (the lane's table: Ethereum L1 at the Sep 2026 cost USD 36 a day, at the Dec 2025 cost 288; year-1 emission 13,700 at USD 0.005, 54,800 at 0.02, 273,800 at 0.10). The cost curve falls 3x to 30x a year, so dollars per proof fall as fast as volume rises. The design's own claim stays the defensible one: a second income that keeps cards on after the subsidy fades (spec 5.10.2), never the main one by 2030.
|
||||
|
||||
Evidence: `docs/analysis/horizon/frontier.md` section 3.11 and the summary (section 7), 6 October 2026; the tracker (ethproofs, "sub-half-cent" fields, September 2026, secondary); the emission schedule (31.688 IGN a block in year 1).
|
||||
|
||||
### E20. "Proofs at the cost of power" is the electricity, not the price
|
||||
"Your cards' electricity is cheap, fine. The price a prover must charge is the lottery income it gives up while it proves, and that scales as one over the network's hash. At your devnet's 1.16 GH/s every quote is a hundred times the market. 'Marginal cost close to power' and 'priced in dollars per proof' are both unconditioned."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` sections 3.1 and 4.1, proposal 3): `site/litepaper.html`, The problem ("a supplier whose electricity cost is close to power and whose price is the subsidy it forgoes, which falls as the network's hash grows"), Building on Igneum ("Proofs priced by the subsidy forgone", with the price as a formula in network hash: per shard, card hash over network hash x 0.8 x 31.688 IGN x shard seconds, plus electricity; 100 to 300x the published market rate at 1.16 GH/s, competitive near 100 GH/s beside the miner, approximate beyond the one card measured), the payment-routes row 4 ("at or above the subsidy the prover forgoes ... never a fixed number"), For miners, Economics and the questions list. The price is published as a formula, never a number. Supersedes P6's stated sentence ("a supplier whose marginal cost is close to power"), which the text check now carries in the conditioned form.
|
||||
|
||||
Answer: Correct. Electricity is under a cent per billion cycles on every card; the price is `h/N` x the subsidy per shard. At 100 GH/s a card proving alone is at 1 to 3x the published market and a card beside its miner at 0.2 to 0.4x, the only row where Igneum undercuts the market, and it rests on the 4% hash loss measured on one card. Consequence per tier: at launch a home miner earns more hashing than proving for outsiders at any card size; a rig the same; the proving market is upside for the fleet as a whole only as network hash grows.
|
||||
|
||||
Evidence: `docs/analysis/horizon/economy-and-utility.md` sections 3.1 (the cost model), 4.1 (the table per card and scale) and proposal 3, 6 October 2026; the Boundless median of USD 0.21 per billion cycles (`developer-adoption.md` 2b, approximate).
|
||||
|
||||
### E21. The dev fee is 1 percent of the producer share, and the funding plan's ceiling took all rewards
|
||||
"Your funding plan says the 1 percent fee could be USD 48,000 to 963,000 a year on year-one rewards of 963 million IGN. The fee template only moves the producer payout. The pool is paid per record. Your ceiling is a quarter too high, and the litepaper never says which share the fee is of."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` section 4.4 and proposal 7): `site/litepaper.html`, the payment-routes row 6 and the Ember section read "default-on, switchable, 1 percent of the producer share"; `docs/plans/funding.md` section 4's ceiling is 1 percent of the producer share, USD 38,520, 154,080 and 770,400 a year at USD 0.005, 0.02 and 0.10 per IGN with every miner on the official client, corrected from 48,000, 193,000 and 963,000.
|
||||
|
||||
Answer: Correct. The fee block carries the dev address in the producer output only; the 20% pool output and the per-record escrow are untouched, so the fee's base is 80% of emission. Consequence per tier: a miner on Ember with the fee on gives up 1 in 100 of its own block rewards and nothing of its proving pay; off with one flag, the same on every tier.
|
||||
|
||||
Evidence: `docs/analysis/horizon/economy-and-utility.md` section 4.4 (`devfee_out.md`) and proposal 7, 6 October 2026; the fee measured on a test network, 4 October 2026 (bench-log: 9 fee blocks in 785).
|
||||
|
||||
## 5. Governance and the founders
|
||||
|
||||
### G1. No cryptography team
|
||||
|
|
@ -540,8 +623,14 @@ Evidence: design doc, decisions table row "Who are you?". Journey: `site/journey
|
|||
### G4. No admin keys, except in everything that matters
|
||||
"'There are no admin keys.' The DEX, the lending market, the bridge and the dev fund contract all ship from your team. Bridges are where the admin keys live."
|
||||
|
||||
Status: Conceded, stated (7 October 2026, morning): the one-screen home page is back on the owner's word, so this row is stated on `site/litepaper.html` only; the sentence there is unchanged and the text check lists it under the litepaper.
|
||||
|
||||
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Governance, "There are no admin keys in consensus"; `site/index.html`, tile "admin keys in consensus". Was: Conceded, wording fix needed.
|
||||
|
||||
Status: Conceded, stated (6 October 2026, later that night): the home page was restored to its fuller shape on the owner's word ("revert it but polish it") and carries the tile "admin keys in consensus" again, beside the litepaper's Governance sentence.
|
||||
|
||||
Status: Conceded, stated (6 October 2026, night): the home page was redrawn as one statement, the live scene, three facts and the downloads, so the tile "admin keys in consensus" is no longer on `site/index.html`; the sentence stands on `site/litepaper.html`, Governance ("There are no admin keys in consensus"). The 5 October line below is the history.
|
||||
|
||||
Answer: Correct. Consensus has no admin keys. The genesis apps are contracts and each will have an upgrade policy; the bridge's is the one that matters, and if it starts as a committee bridge (E7) it has keys by definition. The development fund contract named in the quote no longer exists: the fund was removed on 3 October 2026. The litepaper's sentence must be scoped to consensus and each genesis contract must publish its key policy before launch.
|
||||
|
||||
Evidence: litepaper "Governance". Fix: overclaims list, item 46.
|
||||
|
|
@ -590,6 +679,15 @@ Evidence: design doc Finality v2, Residual risks bullet 3.
|
|||
|
||||
---
|
||||
|
||||
### G15. Three signalling thresholds, four numbers across the documents
|
||||
"Spec 5.7 says 90 percent for an upgrade, 5.5 says 60 percent for a parameter, the class-change rule says 95 with a floor, the project rules file says 90 and 60, and the litepaper's Mining section says a 90 percent signal turns a spare defence on, which is a class change your own rule sets at 95. Pick one sentence and put it everywhere."
|
||||
|
||||
Status: Conceded, stated (6 October 2026, evening; the Horizon economy lane `docs/analysis/horizon/economy-and-utility.md` proposal 4, lane 3's signalling results): one sentence in `site/litepaper.html`, Governance ("Miners set what genesis leaves open") and Mining ("Miners hold the switch"): miners signal three things at three thresholds, 60 percent of blue blocks over two weeks for a parameter genesis leaves open, 90 percent for an upgrade (new code), and 95 percent with a floor height for a class change. The project rules file carries the same sentence (the coordinator's commit of 6 October 2026). Spec 5.5 (60) and 5.7 (90) agree with it; the 95-with-floor rule is the class v4 cut's P2 rule.
|
||||
|
||||
Answer: Correct. The three numbers are three different things: a parameter is a dial inside rules genesis fixed, an upgrade is new code every node must run, a class change moves the hash itself and so takes the highest bar with a floor height as the backstop against a holdout. The lane's game (section 4.3): a 6 percent holdout costs near zero and buys only delay to the floor; a 30 percent pool holds a veto over upgrades at 90 and over class changes until the floor.
|
||||
|
||||
Evidence: `docs/analysis/horizon/economy-and-utility.md` section 4.3 and proposal 4, 6 October 2026; spec 5.5, 5.7, 5.8; the class v4 cut's status file (gates P1 and P2).
|
||||
|
||||
## 6. Comparisons
|
||||
|
||||
### C1. vs Monero: GPUs were excluded on purpose
|
||||
|
|
@ -604,8 +702,18 @@ Evidence: litepaper "For miners", hardware paragraph. Wording: overclaims list,
|
|||
### C2. vs Monero: "no chip in seven years" is not proof
|
||||
"Absence of a public RandomX ASIC is not evidence one cannot exist. Monero is also a small prize."
|
||||
|
||||
Status: Reopened as conceded (7 October 2026, morning, X36): the X9 never shipped, so Monero's record is again seven years without a shipped chip, and the concession stands as first written; the litepaper says so in the same sentences.
|
||||
|
||||
Status: Conceded, stated (7 October 2026, morning): the one-screen home page is back on the owner's word, so this row is stated on `site/litepaper.html` only; the sentence there is unchanged and the text check lists it under the litepaper.
|
||||
|
||||
Status: Closed by the fact (6 October 2026, night): a public RandomX chip now exists, Bitmain's Antminer X9, shipping from July 2026 (1 MH/s at 2,472 W, about USD 5,600; monero-project/monero issue 10270). Every sentence that leaned on its absence was rewritten under X34; "2019 (approximate)" stays on both pages.
|
||||
|
||||
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Mining, "no chip publicly shipped, approximate" and "Monero is precedent, not proof"; `site/index.html`, hero, "a chip gains too little to take your place". Was: Conceded, label needed.
|
||||
|
||||
Status: Conceded, stated (6 October 2026, later that night): the restored home page carries the RandomX paragraph again, "since 2019 (approximate)", beside the litepaper.
|
||||
|
||||
Status: Conceded, stated (6 October 2026, night): the home page no longer carries the RandomX paragraph; "since 2019 (approximate)" and "precedent, not proof" stand on `site/litepaper.html` (vs RandomX, Mining). The 5 October line below is the history.
|
||||
|
||||
Answer: True. "No chip publicly shipped, approximate" is the defensible phrasing. The argument from Monero is precedent, not proof, and the bounty exists because precedent is not proof.
|
||||
|
||||
Evidence: none. Fix: overclaims list, item 17.
|
||||
|
|
@ -817,6 +925,8 @@ Sweep (5 October 2026, evening): stated. `site/index.html`: hero "See the miner"
|
|||
|
||||
Status: Conceded in part, labelled, stated (5 October 2026, night): `site/index.html`, the sentence "All of it will be on this page, live" is no longer on the page; the proofs feed now ends "Live rows arrive with the public testnet, August 2027" (overclaim 61). Was: Conceded in part, labelled.
|
||||
|
||||
Status: Conceded in part, labelled, stated (6 October 2026, night): the proofs feed moved to the litepaper's proving section with the home-page redesign and reads "Live rows arrive with the public testnet. The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes." (X31). The 5 October line below is the history.
|
||||
|
||||
Answer: The tagline plays on "proven" as in ZK proofs and "cupel". The homepage marks every live panel "PREVIEW", "prototype" or "at testnet", which is honest. The sentence "All of it will be on this page, live" is a promise about a future testnet and should say when. The tagline stays; nothing in the ledger depends on it.
|
||||
|
||||
Evidence: `site/index.html`. Fix: overclaims list, item 62.
|
||||
|
|
@ -869,8 +979,14 @@ Sweep (5 October 2026, evening): stated in part. `site/partials/footer.html` on
|
|||
### X8. Exchange listings as a roadmap item
|
||||
"'Mainnet fair launch: Genesis with no premine, 30-day ramp, exchange listings after.' You put listings in the roadmap."
|
||||
|
||||
Status: Conceded, stated (7 October 2026, morning): the one-screen home page is back on the owner's word, so this row is stated on `site/litepaper.html` only; the sentence there is unchanged and the text check lists it under the litepaper.
|
||||
|
||||
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Roadmap phase 6, and `site/journey.json` phase 6, "No listing is arranged, promised or sought by the project". Was: Conceded, fix now.
|
||||
|
||||
Status: Conceded, stated (6 October 2026, later that night): the restored home page shows the journey again; phase 6 reads "No listing is arranged, promised or sought by the project" there and in the litepaper's roadmap.
|
||||
|
||||
Status: Conceded, stated (6 October 2026, night): the home page's journey is no longer shown (the inlined feed remains in the page source); the sentence "No listing is arranged, promised or sought by the project" stands on `site/litepaper.html` Roadmap phase 6 and in `site/journey.json`. The 5 October line below is the history.
|
||||
|
||||
Answer: Correct. No listing is arranged, promised or sought by the project. The phrase comes off the roadmap and the homepage journey.
|
||||
|
||||
Evidence: litepaper "Roadmap", `site/journey.json`. Fix: overclaims list, item 75.
|
||||
|
|
@ -1106,7 +1222,7 @@ Replace with: "A chain built so a chip gains too little to take your place."
|
|||
Replace with: "Benchmark: January 2027"
|
||||
|
||||
61. HP: "All of it will be on this page, live."
|
||||
Replace with: "All of it will be on this page, live, from public testnet in August 2027."
|
||||
Replace with: "All of it will be on this page, live, from the public testnet." (the month was removed on 6 October 2026, X31)
|
||||
|
||||
62. HP economics: "Not one coin to a founder, a fund or a stake."
|
||||
Replace with: "Not one coin of emission to a founder, a fund or a stake. The official client carries a 1% dev fee to the founder's company, as every GPU miner does; any client without it is welcome."
|
||||
|
|
@ -1597,7 +1713,7 @@ Round 2 (5 October 2026, night): the signing half, from block payloads. No RPC e
|
|||
### X15. Remove the founders from a test network and show what continues
|
||||
"'The chain runs without its founders' is a sentence. Take the team's miners, provers, aggregators, seed nodes, observer and site off a running testnet and show what keeps producing blocks, proofs and locks."
|
||||
|
||||
Status: Open, blocked on the public testnet (August 2027 per the litepaper roadmap): next step O-X.2 run at a published time on that testnet, with the protocol already in the Answer below (every project-run node, miner, prover, aggregator and seed stopped, the observer and live page down, 24 hours of blocks per second, proof lag, certificates per hour and a fresh sync from the shipped seed list). Was: Open, experiment scheduled (O-X.2). Sweep (5 October 2026): a public-testnet experiment; not runnable before it exists.
|
||||
Status: Open, blocked on the public testnet (weeks away, when the go checklist closes; see X31): next step O-X.2 run at a published time on that testnet, with the protocol already in the Answer below (every project-run node, miner, prover, aggregator and seed stopped, the observer and live page down, 24 hours of blocks per second, proof lag, certificates per hour and a fresh sync from the shipped seed list). Was: Open, experiment scheduled (O-X.2). Sweep (5 October 2026): a public-testnet experiment; not runnable before it exists.
|
||||
|
||||
Answer: Correct, and it is the right test for the litepaper's sentence (Governance). The test: on the public testnet, at a published time, stop every node, miner, prover, aggregator and seed node the project runs, take the observer feed and the live page down, and record for 24 hours: blocks per second, proof lag, certificates per hour, and a fresh node syncing from the seed list in the client (spec 10.6). What continues is what the sentence may claim. Dependencies the test will expose: the seed list, the release key (G7), the reference pool, the VDF evaluators (every node ships one, 4.5) and the founders' own hashrate share (E2).
|
||||
|
||||
|
|
@ -2130,6 +2246,89 @@ Answer: Correct at discovery; fixed before this document was written. The eviden
|
|||
|
||||
Evidence: the commits above. Experiment: `curl https://igneum.network/api/live` holds no address, no 64-hex key hash and no payout address.
|
||||
|
||||
### X31. The public testnet dated "August 2027" on the site
|
||||
"The litepaper's For miners section said 'Pools and the public testnet are August 2027', the proving section said 'Live rows arrive with the public testnet, August 2027', the roadmap's phase 5 read 'Aug to Oct 2027' and the home page's journey carried the same row. igneum-testnet-1's genesis is final, three seed nodes and the public RPC are up, and the testnet opens when the go checklist (docs/plans/testnet-go.md) closes, which is weeks away."
|
||||
|
||||
Status: Fixed, stated (6 October 2026, night, the owner's decision): every mention of the month is gone from the site. The sentence everywhere is "The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes." (`site/litepaper.html` For miners and the proving section, the roadmap row 5 reads "Weeks away: when the go checklist closes", `site/journey.json` phase 5 and the home page's inlined journey carry the same row). No calendar month is given for the testnet; the owner gives one if he wants one.
|
||||
|
||||
Answer: The date was the plan of 3 October 2026 and the chain overtook it: the testnet genesis was fixed on 5 October, the three seeds and rpc.testnet.igneum.network are up, and the remaining work is the go checklist. Rows that quoted the month (X3, O-X.2's blocker note, overclaim item 75's replacement text) read the new sentence by reference to this row.
|
||||
|
||||
Evidence: `docs/plans/testnet-go.md`; `docs/igneum-testnet` notes (genesis 87617621..., seeds seed1 to seed3.testnet.igneum.network, public RPC). Checked by `tools/ci/ledger-text-check.mjs` (the X3 and X31 rows).
|
||||
|
||||
### X32. The roadmap carried calendar months beside a testnet that is weeks away
|
||||
"After X31 the roadmap read phase 4 'Apr to Jul 2027' and phase 6 'Nov 2027' with phase 5 'weeks away' between them, and phases 1 to 3 carried 'Oct to Nov 2026', 'Nov 2026 to Jan 2027' and '20 nodes by Mar 2027'. A reader spots the contradiction at once."
|
||||
|
||||
Status: Fixed, stated (6 October 2026, night, the owner's ruling): every calendar month is out of the roadmap. Each phase is worded by its gate in the shape phase 5 has: 1 "Under way; closes when the specification is out for external review", 2 "Under way; closes at its gate", 3 "Live since 3 October 2026; closes at its gate", 4 "Closes when the finality design passes external review and one rollup signs for the testnet", 5 "Weeks away: when the go checklist closes", 6 "After the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on time". The order is unchanged. The roadmap's lead reads "Six phases from specification to a fair launch, with four public gates and no calendar dates: each phase closes at its gate." (`site/litepaper.html` Roadmap, `site/journey.json`, the home page's inlined journey). The 3 October 2026 start of the devnet is a fact, not a target, and stays.
|
||||
|
||||
Answer: Dates were the plan of 3 October 2026; the gates are the plan. "Dates slip. Gates do not." was already the roadmap's own sentence, and the roadmap now says only the gates. The owner gives a month if he wants one.
|
||||
|
||||
Evidence: `site/litepaper.html` Roadmap; `site/journey.json`; X31.
|
||||
|
||||
### X33. The public benchmark dated "January 2027"
|
||||
"After X31 and X32 the litepaper still said the public benchmark with a leaderboard 'is January 2027' (For miners) and 'ships in January 2027' (Questions miners ask), a calendar month beside a roadmap that names none."
|
||||
|
||||
Status: Fixed, stated (6 October 2026, night, the owner's ruling): both sentences read "The public benchmark with a leaderboard ships with the public testnet." (`site/litepaper.html`, For miners and Questions miners ask). The gate is one the reader already knows from the roadmap's phase 5. The 5 October answers in M1, X1 and the overclaims list that name January 2027 are the history of the plan and stay as written.
|
||||
|
||||
Answer: The month was the plan of 3 October 2026. The benchmark tool is part of the testnet's go checklist, so it ships with the testnet; no month is given for either.
|
||||
|
||||
Evidence: `site/litepaper.html`; `docs/plans/testnet-go.md`; X31, X32.
|
||||
|
||||
### X34. RandomX described as chip-free
|
||||
"The home page said the random program 'has kept chips off Monero since 2019', the litepaper said Monero ran on RandomX 'with no chip publicly shipped' and spoke of 'Monero's seven years without a public chip'. Bitmain's Antminer X9, a RandomX chip, ships from July 2026 (1 MH/s at 2,472 W, about USD 5,600; monero-project/monero issue 10270), and RandomX 2.0 shipped on 25 March 2026. Every sentence that said or implied RandomX is chip-free, or that Monero's approach has held, was wrong."
|
||||
|
||||
Status: Corrected (7 October 2026, morning, X36): the X9 never shipped. Bitmain opened pre-orders on 26 December 2025 and withdrew the product in mid-May 2026 before any unit was delivered; every sentence below that had it shipping now states that, and RandomX stands as a technique no chip has yet shipped against. The sentences in this row are the history.
|
||||
|
||||
Status: Fixed, stated (7 October 2026, morning): the one-screen home page carries no RandomX sentence, so the corrected wording stands on `site/litepaper.html` (four sentences and the table row); the text check lists them there.
|
||||
|
||||
Status: Fixed, stated (6 October 2026, night, from the cryptanalysis research): four sentences corrected, each with the X9 as the stated fact and its date; every sentence that only names the technique stands.
|
||||
- `site/index.html`, Monero's technique card. Was: "The random program that has kept chips off Monero since 2019 (approximate), rebuilt for graphics cards. The program keeps changing on a schedule fixed at genesis; nobody touches it." Now: "Monero's random program held chips off from 2019 (approximate) until Bitmain's Antminer X9 shipped in July 2026: 1 MH/s at 2,472 W, about USD 5,600. Igneum rebuilds the idea for graphics cards with a program that changes every hour on a schedule fixed at genesis; nobody touches it."
|
||||
- `site/litepaper.html`, Mining. Was: "Monero has run on RandomX since 2019 with no chip publicly shipped, approximate; that is precedent, not proof." Now: "Monero ran on RandomX from 2019 (approximate) with no chip publicly shipped until Bitmain's Antminer X9 in July 2026 (1 MH/s at 2,472 W, about USD 5,600; monero-project/monero issue 10270). About seven years of hold and then a chip: that is the record Igneum's hourly program and its chip model are built against."
|
||||
- `site/litepaper.html`, vs RandomX. Was: "RandomX has kept chips off Monero since 2019 (approximate) by making the mining program random, so the only hardware that runs it well is the hardware everyone already owns." Now: "RandomX kept chips off Monero from 2019 (approximate) by making the mining program random, so the hardware that ran it well was the hardware everyone already owned. That hold ended: Bitmain's Antminer X9 ships from July 2026 at 1 MH/s and 2,472 W, about USD 5,600, and RandomX 2.0 shipped on 25 March 2026 (monero-project/monero issue 10270)."
|
||||
- `site/litepaper.html`, What Igneum does not claim, "A chip is impossible". Was: "Monero's seven years without a public chip are precedent, not proof, and a small prize: they say nothing about the price of a chip with the 256 MB cache on its die, and that price is a cost model, not a measurement." Now: "Monero's RandomX held for about seven years before Bitmain's Antminer X9 shipped in July 2026 (1 MH/s at 2,472 W, about USD 5,600; monero-project/monero issue 10270). That record says nothing about the price of a chip with the 256 MB cache on its die; that price is a cost model, not a measurement."
|
||||
- `site/litepaper.html`, vs RandomX table, Track record. Was: "No chip publicly shipped in seven years, approximate". Now: "About seven years without a public chip, then Bitmain's Antminer X9, shipping from July 2026".
|
||||
- `site/litepaper.html`, Precedents table, the RandomX row gains "its first chip, Bitmain's Antminer X9, ships from July 2026".
|
||||
Checked by `tools/ci/ledger-text-check.mjs` (rows X34 on both pages). C2 carries the same update.
|
||||
|
||||
Answer: The precedent Igneum cites is now a complete one: a fixed random program held CPU mining for about seven years and then a chip shipped. Igneum's program changes every hour from a genesis-fixed schedule, its dataset grows, and the chip model on the numbers page prices the chip that stores the dataset rather than assuming none can be built. The X9's rate and power are Bitmain's published figures, not our measurement.
|
||||
|
||||
Source: github.com/monero-project/monero/issues/10270 (the X9 tracking issue; rate, power, price and the July 2026 ship date as stated there); RandomX 2.0 release, 25 March 2026. The year 2019 for RandomX's activation stays labelled approximate.
|
||||
|
||||
Evidence: `site/index.html`; `site/litepaper.html`; `tools/ci/ledger-text-check.mjs`; C2.
|
||||
|
||||
### X35. The class v4 chip headline stated as one number, 2.1x
|
||||
"The home page said the strongest chip reaches 'about 2x once the lever now in its gates ships' and the litepaper said the latency-shadow work 'brings the chip to about 2x' and that its edge 'falls from 5.6x to 2.1x ... at a chip core equal to the GPU's'. That 2.1x assumes the chip's core costs what the GPU's does per operation (k = 1). Bitmain's Antminer X9 reached about a third of its honest device's energy on a latency-bound random program, so k about 0.33 is a shipped product class, and at that k the same model gives 3.9x."
|
||||
|
||||
Status: Kept, relabelled (7 October 2026, morning, X36): the range stands, with k about 0.33 labelled as the X9's claimed, unmeasured core, since no unit shipped or was benchmarked.
|
||||
|
||||
Status: Fixed, stated (7 October 2026, 00:0x UK, from the ladder lane's recalibration against the X9): every public sentence that stated 2.1x alone now states the range with k named. The 5.7x class v3 memory-only figure has no core work in it and is unmoved; the litepaper's 5.6x is the Counter ASIC 3.0 item 8 figure and stays as cited.
|
||||
- `site/index.html`, chip model card. Was: "In our public model the strongest chip reaches 5x to 9x per joule against an RTX 5090 today, about 2x once the lever now in its gates ships." Now: "In our public model the strongest chip reaches 5x to 9x per joule against an RTX 5090 today. With the class v4 shadow work it is 2.1x to 3.9x, the range running from a chip core as costly per operation as the GPU's (k = 1) to one as efficient as Bitmain's RandomX chip (k about 0.33); the ladder's second rung takes that 3.9x to about 2.8x."
|
||||
- `site/litepaper.html`, "A chip is impossible" (both copies). Was: "it brings the chip to about 2x." Now: "it brings the chip to 2.1x to 3.9x, the range running from a chip core as costly per operation as the GPU's (k = 1) to one as efficient as Bitmain's Antminer X9 (k about 0.33); the ladder's second rung takes the X9 bracket to about 2.8x." Was: "falls from 5.6x to 2.1x on GDDR7 at a chip core equal to the GPU's, the 5090 at 0.2% less rate". Now: "falls from 5.6x to 2.1x on GDDR7 at a chip core equal to the GPU's (k = 1) and to 3.9x at the X9's core (k about 0.33), the 5090 at 0.2% less rate".
|
||||
- `docs/fud-ledger.md` M32's answer: the 2.1x at k = 1 now carries 3.9x at k about 0.33 beside it (and 1.7x beside the Apple M5 Max's 0.9x).
|
||||
- The ladder itself is described on the litepaper's Mining section ("The work that waits can grow") and in M34 ahead of the code shipping (0.3.17, behind an activation height).
|
||||
Checked by `tools/ci/ledger-text-check.mjs` (rows X35 on both pages).
|
||||
|
||||
Answer: One number was the model's k = 1 column; the X9 made the k = 0.33 column a product rather than a claim, so the public figure is the range. Rung 2 of the ladder (the top admissible rung on 6 October 2026) takes the X9 bracket from about 3.9x to about 2.8x and does not close it; the ladder moves at the pace of the cards that pay for it (M34).
|
||||
|
||||
Evidence: `docs/design/latency-ladder.md` (the k column, the rung table at k = 0.33, the verifier table); M34; X34.
|
||||
|
||||
### X36. The X9 described as a shipping chip
|
||||
"X34 and X35 said Bitmain's Antminer X9 'ships from July 2026' and called it 'the shipping RandomX chip'. It never shipped: Bitmain opened pre-orders on 26 December 2025 for July 2026 delivery, resellers told buyers in mid-May 2026 that Bitmain had discontinued it and refunded them, no unit was delivered and none was independently benchmarked."
|
||||
|
||||
Status: Fixed, stated (7 October 2026, morning, from the research agent's primary sources): every public sentence that had the X9 shipping now states the pre-order, the withdrawal and the unbenchmarked core.
|
||||
- `site/litepaper.html`, Mining. Was: "Monero ran on RandomX from 2019 (approximate) with no chip publicly shipped until Bitmain's Antminer X9 in July 2026 (1 MH/s at 2,472 W, about USD 5,600; monero-project/monero issue 10270). About seven years of hold and then a chip: that is the record Igneum's hourly program and its chip model are built against." Now: "Monero has run on RandomX since 2019 (approximate) with no chip shipped. Bitmain opened Antminer X9 pre-orders on 26 December 2025 for July 2026 delivery, then withdrew the product in mid-May 2026 and refunded buyers before any unit shipped; none has been independently benchmarked. A box with about a 2x per joule edge over the best CPUs, and about 3x over a desktop, was withdrawn rather than face a RandomX re-tune of 1.5x or more. That is the band Igneum's class v4 model sits in (2.1x to 3.9x over an RTX 5090), and the defence that held was a maintained algorithm with a credible upgrade path, which is what the ladder is."
|
||||
- `site/litepaper.html`, vs RandomX. Was: "That hold ended: Bitmain's Antminer X9 ships from July 2026 at 1 MH/s and 2,472 W, about USD 5,600, and RandomX 2.0 shipped on 25 March 2026". Now: "The one chip announced against it, Bitmain's Antminer X9 (pre-orders from 26 December 2025 at 1 MH/s and 2,472 W, about USD 5,600), was withdrawn in mid-May 2026 before any unit shipped; RandomX 2.0 shipped on 25 March 2026".
|
||||
- `site/litepaper.html`, vs RandomX table, Track record. Was: "About seven years without a public chip, then Bitmain's Antminer X9, shipping from July 2026". Now: "About seven years without a shipped chip; the one announced, Bitmain's Antminer X9, was withdrawn in May 2026 before launch".
|
||||
- `site/litepaper.html`, "A chip is impossible". Was: "Monero's RandomX held for about seven years before Bitmain's Antminer X9 shipped in July 2026 (...)". Now: "Monero's RandomX has held for about seven years; the one chip announced against it, Bitmain's Antminer X9, was withdrawn in mid-May 2026 before any unit shipped, its claimed core (k about 0.33) never measured."
|
||||
- `site/litepaper.html`, Precedents row: "its first chip, Bitmain's Antminer X9, ships from July 2026" is now "the one chip announced against it, Bitmain's Antminer X9, withdrawn in May 2026 before launch". The X35 range sentences (both copies) now read "the core Bitmain claimed for its withdrawn Antminer X9 (k about 0.33, never measured)" and "the X9's claimed core (k about 0.33, never measured)". The ladder paragraph (M34) reads "the one Bitmain claimed for its withdrawn Antminer X9 (about 3x per joule over a desktop CPU, never measured)".
|
||||
- `site/index.html`, chip model card: "to the core Bitmain claimed for its withdrawn Antminer X9 (k about 0.33, never measured)".
|
||||
Checked by `tools/ci/ledger-text-check.mjs` (rows X36 on both pages; the X34 and X35 phrases updated).
|
||||
|
||||
Answer: The k about 0.33 column stays in the public range as the X9's claimed, unmeasured core: Bitmain's figures (1,000 KH/s at 2,472 W, 2.47 J/KH) were a pre-order sheet, never a benchmark, and the RandomX team's own reading (sech1, 25 January 2026) was "no, X9 is not an ASIC... Only 2x efficiency gap (hash/Joule) is not 'cracked'": a box of commodity Sophgo SG2044 server SoCs with an AES block and over sixty DRAM sticks, no tapeout, about 2x per joule over a tuned Zen 4 part and about 3x over a stock desktop CPU. It was withdrawn rather than face a RandomX re-tune of 1.5x or more. The lesson the public text now carries is that one: a maintained algorithm with a credible upgrade path held, which is what the latency ladder is for Igneum. Monero's hashrate shows no X9 fleet (about 6.1 GH/s before and after, approximate).
|
||||
|
||||
Sources: bitmain.com news post dated 1 January 2026 (pre-orders opened 26 December 2025, USD 5,600, 1,000 KH/s, 2,472 W, shipments "scheduled to begin in July 2026"); shop.bitmain.com (the X9 still listed as sold out, "shipping in July 2026", 7 October 2026); pcpraha.cz, 15 May 2026 ("Antminer X9 canceled: Bitmain withdraws model from market before launch"); r/MoneroMining, 17 May 2026 ("Bitmain's X9 discontinued", the relayed reason "technical adjustments and a new strategy", a reseller's "there may be an algorithm change"); monero.forum, 16 May and 3 June 2026 (jeffro256: benchmark data "pretty hard to get since they're never going to be released publicly"). Bitmain itself published no cancellation.
|
||||
|
||||
Evidence: `site/index.html`; `site/litepaper.html`; `tools/ci/ledger-text-check.mjs`; X34, X35, M34.
|
||||
|
||||
## Status updates, 4 October 2026 (round 4)
|
||||
|
||||
- **F21** (the long-partition fork). Extended: a side locks alone when its own share of its own table reaches two thirds, at `t = W (2/3 - s) / (1 - s)`: 50/50 at 2,400 DAA s on the devnet (about 40 minutes), 10 days on mainnet; the 60 side of 60/40 at 1,200 DAA s (20 minutes), 5 days; the ledger's measured `W/(3R)` = 200 s is this formula at s = 1/2. At HEAD a second certificate at an index is kept, logged and ignored (`processes/finality.rs:650-655, 661-666`) and `fork_choice_lock` (`:886-901`) pins the node. Public text: `site/litepaper.html:511` says a third of the blocks is needed to split finality in a partition; the partition alone does it. Replacement sentence in `docs/review/round-4-2026-10-04.md` section 1 (b). Review id R4.1.5.
|
||||
|
|
|
|||
|
|
@ -79,8 +79,10 @@ Also logged for context: the Mac's Linux cross-build with zig (`infra/cross/buil
|
|||
| R2 | Windows exes come from `tools/cross-remote.sh` (fork worktree: igneumd.exe, igneum-miner.exe; app/igneum-app: igneum-app.exe and the two tools). The PC `build` job stays as the second source until two releases have shipped from the box. |
|
||||
| R3 | The Mac keeps what only it can do: aarch64-apple-darwin binaries (the DMG, nodes that agents run locally), tests that need Metal (proto-metal, the Metal worker), and measurements. Those still use `tools/lock/with-lock.sh` and the Mac's build slots. |
|
||||
| R4 | A worktree builds once on the box per commit plus overlay; the next build is incremental in `/srv/builds/<worktree>/.../target`. Nobody deletes another worktree's target dir on the box. |
|
||||
| R4a | A lane's scratch on the box mirror survives other lanes' builds (7 October 2026, after the attack rows lost `attack-f3/`, `attack-f1-venv/` and `tools/attack/*/target` to each other's builds, hazard AP-H1). The checkout's `git clean` spares `attack-*`, `scratch-*`, `target-attack-*` and `.build-remote.log` at any depth, plus every glob in the mirror-local file `/srv/builds/<worktree>/.igneum-scratch-spare` (one glob per line, gitignore syntax, `#` comments; the file itself is spared). **To declare a prefix**: before the first build that must leave it alone, append one line named for the lane (`bs-<name>`, `r03xx-ship`, the scratchpad prefix rule of 7 October): `ssh -i ~/.ssh/igneum_ed25519 build@188.40.146.49 "printf 'bs-mylane-*\n' >> /srv/builds/<worktree>/.igneum-scratch-spare"`. Keep scratch outside the crate directories the overlay syncs (`bs_overlay_dir` rsyncs those with `--delete`, so an undeclared dir inside one goes the Mac's way regardless; a `target-attack-*` name is safe there too, rsync protects its excluded `target-*/`). `remote-run.sh --self-test` shows a declared and a fixed-prefix dir surviving and an undeclared one removed; `tools/ci/scratch-spare-check.sh` (pre-push gate) fails when the clean line loses the mechanism or gains `-x`. |
|
||||
| R5 | `RUST_TOOLCHAIN` in provision.sh is bumped in the same commit as the Mac's `rustup update`; build-remote.sh refuses a mismatch. Add a `rust-toolchain.toml` to the fork and the repo (none exists today) so both sides pin from one file. |
|
||||
| R6 | The box is never a node host for the live devnet and never holds a secret (no `~/.config/igneum` there). The Devnet 2 seed on it runs under its own unit with `--devnet --devnet-suffix=<n>` on 26611 (ufw already open) when that work starts. |
|
||||
| R6a | ONE recorded exception to R6 (main's ruling, 6 October 2026, 20:0x UK): the three Discord webhook URLs live at `/srv/discord-hooks/env` (mode 600, owner build), installed by `infra/build-server/discord-hooks/install.sh` with `IGNEUM_SECRET_ON_BOX_OK=1`, because the Mac sleeps and the bot's timer must not. The reasoning: a webhook URL signs no release, moves no funds and reaches none of the devnet's hands; whoever holds it can only post as the bot, and a leaked one is deleted in Discord in one click and the file replaced. No other secret joins it; `check` prints key names, never values. |
|
||||
| R7 | CLAUDE.md line "nothing is built on a server" and "Windows builds go to the GitHub runner, Linux binaries come from infra/cross/build-linux.sh" are rewritten when R1 and R2 are adopted; until then the box is the measured option, not the rule. |
|
||||
|
||||
## 5. Gotchas met on the first day
|
||||
|
|
@ -88,17 +90,270 @@ Also logged for context: the Mac's Linux cross-build with zig (`infra/cross/buil
|
|||
| Case | What happened | Fix |
|
||||
|---|---|---|
|
||||
| Stale overlay blocks the next checkout (PC 1 worker, 6 Oct 2026) | build-remote.sh rsyncs uncommitted files over the box's checkout; on the next commit `git checkout -B` refused with "local changes would be overwritten" | remote-run.sh `checkout` mode: `git checkout -- .` and `git clean -fd` (target dirs, sha stamps and ignored files kept) before the branch checkout, then the overlay; `remote-run.sh --self-test` reproduces the dirty tree and shows the mode landing on the new commit clean |
|
||||
| The same class met twice more by the UI lane on its mirror (worktrees whose tools predate 3e6a488 pipe the OLD remote-run.sh to the box per build) | nothing builds until the tree is cleared by hand | the fix is in master's remote-run.sh since 3e6a488; a worktree gets it by merging master; `tools/ci/mirror-reset-check.sh` (in the pre-push gate) reads checkout_tree() and fails when the reset and the clean do not both come before the branch checkout; its self-test shows the real script passing, a copy without the reset failing, and the same lines moved after the checkout failing. cargo-audit is owned by provision.sh step_cargo_tools (1eec354, the night battery's agent), confirmed `ok (cargo-audit 0.22.2)` on the box |
|
||||
| An all-identical overlay listed only directories | the first run's touch pipeline got an empty file list and failed | files only are counted and re-stamped (lib.sh bs_overlay_dir) |
|
||||
| bash 3.2 on the Mac treats an empty array as unbound under `set -u` | run-from-mac.sh died on `PASS[*]` | a string instead of an array |
|
||||
| `grep -q` plus `pipefail` turned a strings hit into a miss | the commit-string gate failed a stamped igneumd on its first use (SIGPIPE on `strings`) | `grep -c` |
|
||||
| A path dependency inside a vendor repo (the shipper's proving build, 6 Oct 2026) | proving/igneum-prove depends on vendor/igneum-node-exec/igneum/evm-types, a MEMBER of the fork's workspace (it inherits `thiserror` from the fork's root manifest); the first design synced that one directory, so cargo found no workspace root on the box ("failed to load manifest for workspace member"), and the shipper cross-built the Linux prove-host on the Mac with cargo-zigbuild meanwhile | lib.sh groups path dependencies by git top level: one under vendor/ is a whole repository, pushed to its mirror (a fork worktree such as igneum-node-exec goes to /srv/igneum-node.git, which already held its branch; a repository of its own gets /srv/<name>.git, created on first use) and checked out whole at /srv/builds/<worktree>/vendor/<name>; run-from-mac.sh wires every vendor repo the Cargo.toml files reach (today only igneum-node-exec). The detector's first version tested "under BS_TOP" before "own repository" and missed it, since vendor/ lies under the igneum top level on disk. Then `libprotobuf-dev` was missing (sp1-prover-types's build script imports google/protobuf/empty.proto); added to provision.sh. Proof: `cd proving/igneum-prove && tools/build-remote.sh -- build --release -p igneum-prove-host`: igneum-prove-host 71,943,192 B, sha256 e9213e3a6c979512d7859f6d8e848105bab53f4355e99fb0d30fb4a72c2d5714, ELF x86-64, 1 min 05 s warm (the cold run compiled 605 crates in 55 s before protoc stopped it). A Mac worktree has no vendor/ of its own, so a worktree that builds proving needs `git -C vendor/igneum-node worktree add <wt>/vendor/igneum-node-exec execution-layer` first, as the fork worktrees do |
|
||||
| One remote build lost its ssh session after 75 s (18:30:50 UTC, the first full proving build) | the remote bash died with it (slot line left behind, no JSONL line); no OOM, no reboot, the retry a minute later passed | the dashboard collector's one-pass unit finished within a second of the drop, so it was tested: a 90 s remote session through the same ControlMaster path survived two collector passes triggered by hand; the collector only reads (/proc, lock files, `flock -n`, `sccache --show-stats`, `kill(pid, 0)`). One event, no cause in the journal, the retry passed. A build that must survive a dropped connection would need the remote command under setsid with the Mac reconnecting to wait; not done, open if it happens again |
|
||||
| A stamp file at a nested path failed every checkout of /srv/builds/igneum (18:31 to 18:5x UTC, the shipper's 18:52 run) | a repo-kind crate keeps its `.build-remote-sha-<target>` and `target/` inside the crate dir; the checkout mode's clean-tree test only excused them at the tree root | the test is depth-agnostic and uses `--untracked-files=all` (a wholly untracked directory is otherwise collapsed to `?? dir/`); the self-test carries a nested stamp, a nested target dir and a stale `.git/index.lock`, which the mode now removes when no git runs there |
|
||||
| The attack rows lost scratch dirs to each other's builds (7 Oct 2026, 09:2x UK, hazard AP-H1 from the attack-pass lane) | checkout_tree's `git clean -fd` ran on the shared mirror before every build from any agent and took every untracked directory: `attack-f3/`, `attack-f1-venv/`, `tools/attack/*/target` | the clean spares the fixed prefixes and the globs in `.igneum-scratch-spare` (R4a), still without `-x` so `.git/info/exclude` applies; the clean-tree test asks `git clean -nd` with the same excludes instead of filtering the status list, so a spared dir no longer reads as "not clean"; the self-test carries a fixed-prefix dir at the root and nested, a declared dir, the spare file and an undeclared dir; `tools/ci/scratch-spare-check.sh` in the gate |
|
||||
| Two runs on one worktree at once (the shipper, 18:48:56Z) | the second run's checkout replaced the first's sources mid-cargo; both died | lib.sh takes a per-worktree lock on the box (`/srv/builds/_locks/wt-<worktree>`, mkdir-atomic, holder line) across sync, build and fetch; a second run waits up to 2 h (a line every minute), a lock older than 3 h is taken over; released on EXIT. The build slot (`build-<k>`) is unchanged |
|
||||
| The 0.3.15 prover pair for the PCs needs `--features igneum-prove-host/cuda` (the PCs run SP1_PROVER=cuda) | my first proving build named no feature | built on the box from master e1b5bc9: igneum-prove-host 73,161,528 B sha256 71bc2438856bb141f6cad3d18489f708568144fad5a06a002fbadefb9ce256f9, igneum-prove-export 3,609,360 B sha256 263bf4cef70af4a13a45b2e79b8dbab373282ea4c571f624d02ddb5791935361 (52 s warm, no CUDA needed at build time); handed to the shipper |
|
||||
| Let's Encrypt saw NXDOMAIN for build.igneum.network | the deSEC record was minutes old; Ubuntu's Caddy then fell back to ZeroSSL and failed with HTTP 422 for ever | issuer pinned to Let's Encrypt; the retry got the certificate |
|
||||
|
||||
## 5b. Reproducible builds (main's rule, 6 October 2026, from the 0.3.14 repro docs/evidence/reproduced/0.3.14.md)
|
||||
|
||||
| Class | What differed | Standard fix, in every build path |
|
||||
|---|---|---|
|
||||
| prost's generated `protowire.rs` embeds `OUT_DIR` | two builds in differently named target dirs give different bytes | ONE fixed target path per target: `target` (or the name `--target-dir` gives) on the box, `$CARGO_TARGET_DIR` on the Mac's cross-build.sh, the persistent dir of the PC job; never a per-run name |
|
||||
| libmimalloc-sys compiles mimalloc's C with `__DATE__`/`__TIME__` | two builds a minute apart differ when mimalloc recompiles | `SOURCE_DATE_EPOCH` = the node commit's author time and `TZ=UTC`, exported in lib.sh `bs_repro_env` (build-remote.sh, cross-remote.sh, workers-remote.sh), remote-run.sh (`BR_SDE`, logged in the JSONL line as `source_date_epoch`), proto-cuda/windows-node/cross-build.sh, and the PC job (push-build-inputs.sh writes `node.commit_time`, jobbuild.rs exports it before every cargo build of a stage; unit test asserts it) |
|
||||
| sccache hid both | a cache hit returns the first build's object | the self-test runs with `RUSTC_WRAPPER=/usr/bin/env` (a pass-through; an EMPTY value is "unset" to cargo and would fall back to the configured sccache) |
|
||||
|
||||
Self-test: `tools/build-remote.sh --self-test-repro [--full]` from a fork worktree. Run 6 Oct 20:02 UTC on the box (igneum-node-bs at 3bfe346f, epoch 1791120573): igneum-miner twice a minute apart, kaspa-grpc-core cleaned in between: MATCH 91e130f52438edf012466d3f1d3d634ab9263cd7858e67d2dd8d590b152f8a22; the same build into a per-run target path: 548671e7... (differs, the OUT_DIR class shown firing). `--full` (kaspad with libmimalloc-sys recompiled a minute later, with and without the epoch): run 6 Oct 20:04 to 20:08 UTC (247 s): kaspad with libmimalloc-sys recompiled a minute later, epoch set: MATCH 70219bc29cc98ac75da00702b9966f9f5d73cbf10efaca1c8841c00bb5aae7bf; without the epoch, a minute later: 45169e88... (differs, the __DATE__ class shown firing); the miner line again MATCH 91e130f5..., the per-run path 310383f5... (differs). The box is deterministic with the rule and shown non-deterministic without it
|
||||
|
||||
## 5a. The GPU workers (added 6 October 2026, 19:10 UTC, for the class v4 rehearsal)
|
||||
|
||||
| What | Fact |
|
||||
|---|---|
|
||||
| Headers on the box | provision.sh `step_cuda`: NVIDIA's ubuntu2404 apt repository, `cuda-nvrtc-dev-12-8`, `cuda-cudart-dev-12-8`, `cuda-driver-dev-12-8` (the libcuda stub) and Ubuntu's `opencl-c-headers`; no nvcc (no build file calls it: both workers compile from C/C++ sources with the headers and dlopen libcuda, libnvrtc and libOpenCL at run time), no driver (no GPU here). 12.8 is the minor proto-cuda/nvrtc/fetch-redist.sh pins |
|
||||
| Command | `tools/workers-remote.sh [--out <dir>]` from any igneum worktree: proto-cuda and proto-opencl through the mirror and overlay, clang++ 18 with `-static-libstdc++ -static-libgcc`, both workers, sha256 and the glibc ceiling printed, artefacts in `<worktree>/infra/cross/out-workers-box/` |
|
||||
| Proof (master worker.cpp at 3b2c840) | igneum-worker-cuda 1,613,992 B sha256 6db8a9ad295a9f7598a8b5500f1c271fbfdb9a8df90848b95ff340664007031e; igneum-worker-opencl 124,904 B sha256 0dea75bb8d2d54721ee44b55a3ba486241d3c6053e72a133f8f5e40d2355cbbd; 3 s on the box |
|
||||
| Consequence: glibc ceiling 2.38 | runs on the fleet (Ubuntu 22.04 containers are glibc 2.35: NO, 2.38 > 2.35; Ubuntu 24.04 hosts yes). The Mac's zig build (`infra/cross/build-workers-linux.sh`, glibc 2.36) is the one for Debian 12 and HiveOS; the fleet agent must check its boxes' glibc before swapping the worker. Fix if needed: zig on the box (open row in section 6) or `clang -target x86_64-linux-gnu.2.35` via zig; both a day's work, not done |
|
||||
| Runner fix found on the way | a command string carrying `set -e` leaked into remote-run.sh through `eval` and killed the runner before its RESULT line (reported as rc 101); the runner now evaluates the command in a subshell |
|
||||
|
||||
## 5c. Deploy key for the observer clone (DONE: the project lead added the key on 7 October 2026, morning)
|
||||
|
||||
Done. the project lead added the public half as the read-only deploy key "igneum-build-1 observer (read-only)" (SHA256:51ice3W8...; the
|
||||
organisation's deploy-key policy had to be switched to Enabled first). The sync's own test then still said "mirror": `ssh -T` to
|
||||
GitHub exits 1 after its greeting and the script runs under pipefail, so `su ... | grep -q` reported failure although grep had
|
||||
matched; fixed by capturing the output first (install-hands.sh). First pass reading "source: github (deploy key accepted)":
|
||||
7 Oct 2026 07:30:54 UTC, "observer files changed (8aab05ce); restarting igneum-observer": the clone went from the mirror's 8397781 to
|
||||
GitHub's master 8aab05ce in that pass, the observer restarted on it and is active; remote `github` = git@github-igneum-observer:igneum-network/igneum.git. The clone now follows origin/master every 5 minutes; the mirror stays the fallback whenever GitHub refuses.
|
||||
|
||||
The steps as they were, for the record:
|
||||
|
||||
The observer on the box runs tools/observer from a clone that follows the mirror `/srv/igneum.git`, which moves only when a Mac
|
||||
agent pushes. With a read-only deploy key it follows GitHub directly (every 5 minutes, `igneum-observer-sync.timer`). The key pair
|
||||
was made ON the box by `infra/build-server/hands/install-hands.sh` as user build; the private half is `/srv/observer/.ssh/deploy_igneum`
|
||||
(mode 600), never copied and never printed. The public half is what GitHub gets.
|
||||
|
||||
| Step | Where | What |
|
||||
|---|---|---|
|
||||
| 1 | this Mac | `ssh -i ~/.ssh/igneum_ed25519 build@188.40.146.49 cat /srv/observer/.ssh/deploy_igneum.pub` prints one line starting `ssh-ed25519` and ending `igneum-build-1 observer read-only` (the public half; also at the end of this section) |
|
||||
| 2 | github.com, signed in as the organisation owner (igneum-labs) | https://github.com/igneum-network/igneum/settings/keys, "Add deploy key" |
|
||||
| 3 | the form | Title `igneum-build-1 observer (read-only)`; Key: paste the line from step 1; leave "Allow write access" UNTICKED; "Add key" |
|
||||
| 4 | this Mac | `ssh -i ~/.ssh/igneum_ed25519 root@188.40.146.49 'systemctl start igneum-observer-sync.service; journalctl -u igneum-observer-sync -o cat -n 4'` must print `source: github (deploy key accepted)`; until then it prints `source: mirror (...)` and nothing is broken |
|
||||
|
||||
The sync tests the key with `ssh -T git@github-igneum-observer` on every pass and falls back to the mirror whenever GitHub refuses,
|
||||
so a revoked key never stops the observer; it only makes it follow the mirror again. Public half: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGS9oMQ4E9f0Zx+WTCemiUvun+6G45ecyIW8N1AGJfwi igneum-build-1 observer read-only`
|
||||
|
||||
## 6. What the box does not do yet
|
||||
|
||||
| Gap | Why it matters | Next step |
|
||||
|---|---|---|
|
||||
| No zig / cargo-zigbuild | the devnet seed (Debian 12, glibc 2.36) takes the Mac's zig build; a native box build links glibc 2.39, which Debian 13 seeds accept and HiveOS (Ubuntu 18/20 base) does not | install zig 0.17 + cargo-zigbuild in provision.sh, add `--target x86_64-unknown-linux-gnu.2.36` mode to build-remote.sh |
|
||||
| zig / cargo-zigbuild: DONE 7 Oct 2026 (main's order after a seed took 14 restarts and three minutes down on a glibc 2.39 binary) | provision.sh `step_zig` (zig 0.17.0 from ziglang.org, sha256 of the official index) and cargo-zigbuild 0.23.4 in `step_cargo_tools`; `tools/build-remote.sh --ship [--glibc 2.36]` runs `cargo zigbuild --target x86_64-unknown-linux-gnu.2.36`, fetches from the target-triple dir and runs `tools/ci/glibc-ceiling-check.sh` (need at most 2.36) on every artefact; `tools/workers-remote.sh` builds with `zig cc/c++ -target x86_64-linux-gnu.2.36` by default (`GLIBC=native` for clang). Rule: anything that ships to a seed or a HiveOS rig is built with `--ship`; a plain build is glibc 2.39 for the box and Ubuntu 24.04 hosts only. Proof (fork 3bfe346f, cold through zig, 3 min 17 s): igneumd 47,023,120 B sha256 345dfb95... needs GLIBC_2.34; igneum-miner 9,248,808 B sha256 d09dc27b... needs GLIBC_2.34; the workers through zig at 2.36 (6 s): igneum-worker-cuda 6,759,272 B sha256 690c8e91... and igneum-worker-opencl 307,264 B sha256 329fb6a9..., each needing GLIBC_2.34 and libc only (the first zig attempt died on __isoc23_strtol because `-I /usr/include` for CL/cl.h put the host's glibc 2.39 headers before zig's; the OpenCL headers are reached through a CL-only symlink dir now); the check's self-test fires on 2.38 against 2.36 and passes 2.34 and 2.36 | the Mac's infra/cross/build-linux.sh is the same recipe and can retire once two seed releases shipped from the box |
|
||||
| Ceilings per class (main, 7 Oct 2026 00:2x UTC, after RunPod's Ubuntu 22.04 canaries, glibc 2.35, refused the GLIBC_2.38 box binaries, and HiveOS turned out Ubuntu 20.04 based, glibc 2.31) | one table in `tools/ci/glibc-ceiling-check.sh` (`--class`, `--ceiling-of`): hive and rig 2.31, seed and linux 2.35, native unchecked; `tools/build-remote.sh --ship hive|rig|seed|linux` (default seed) and `tools/workers-remote.sh --class` (default rig) build with zig at the class's glibc and check against it. Proof on the box: fork 3bfe346f at class hive: igneumd 47,023,760 B sha256 2a07dbf1... needs GLIBC_2.30, igneum-miner 9,249,024 B sha256 c113c... needs GLIBC_2.30 (4 min 13 s cold, zig recompiles the C++ for the new glibc); workers at class rig: igneum-worker-cuda 6,761,120 B sha256 db8ed18a..., igneum-worker-opencl 309,096 B sha256 3e18a4f8..., each needing GLIBC_2.17. Run inside docker on the box: ubuntu:20.04 (ldd 2.31) and ubuntu:22.04 (ldd 2.35) both print `igneumd 2.1.0`, the miner's `usage: igneum-miner mine <grpc url> ...` line, `igneum-worker-cuda 1.0 (4 October 2026)` and the OpenCL worker's own `FAIL: libOpenCL.so.1 is not installed (the GPU driver provides it ...)`, which is the binary running in a GPU-less container, not glibc refusing it | the 0.3.17 HiveOS package is the class-hive build (shipper told); docker.io is on the box for this proof (user build in the docker group) |
|
||||
| No macOS target | agents who run nodes on the Mac still build there | out of scope (needs the macOS SDK on Linux); the fleet or the box's own Devnet 2 seed takes the test-network runs instead |
|
||||
| No CI runner | GitHub `ci.yml` and `windows.yml` run on GitHub's machines | install a self-hosted runner as user build once R1 is in |
|
||||
| CI runner: DONE by the box-work agent (actions.runner.igneum-network-igneum.igneum-build-1.service) | | |
|
||||
| Byte identity with the Mac's exes | different C/C++ toolchain (Homebrew mingw vs Ubuntu GCC 13) and embedded source paths | not a goal; the box is identical with itself build to build, cross-remote.sh reports sha256 and the DLL list per exe |
|
||||
| Robot API | `~/.config/igneum/hetzner-token` is the Cloud token (hcloud); the dedicated box lives in Robot, a separate credential | main sets the Robot server name in the UI; a webservice user goes to `~/.config/igneum/robot-credentials` when needed |
|
||||
|
||||
## 7. The box's second shift (6 October 2026, from 20:3x UK, the project lead: "what else can our building machine be working on?")
|
||||
|
||||
Four items, each its own commit on branch `box-work` with its section here. Times UTC.
|
||||
|
||||
### 7.1 The GitHub Actions runner (DONE 19:19Z)
|
||||
|
||||
| Fact | Value |
|
||||
|---|---|
|
||||
| Runner | `igneum-build-1`, actions/runner 2.338.0 (tarball sha256 af4b794c... checked against the release note), registered on igneum-network/igneum at 19:19:49Z, online, labels `self-hosted, Linux, X64, igneum-build-1` |
|
||||
| User | `runner` (uid 1001, own group, no sudo, not in `build`'s group; /home/runner 750). Never `build`, never root. The runner's credential (`/opt/actions-runner/.credentials`, mode 600) is the only thing it holds; it signs nothing and reaches no hand |
|
||||
| Service | `actions.runner.igneum-network-igneum.igneum-build-1.service` (GitHub's `svc.sh install runner`), drop-in `igneum.conf`: Nice 10, IO best-effort 7, Restart on-failure. Agents' builds (nice 0 through build-remote.sh) win the CPU over a CI job |
|
||||
| Toolchains | rustup 1.99.0 pinned like the box (`RUST_TOOLCHAIN`), targets x86_64-unknown-linux-gnu and x86_64-pc-windows-gnu, clippy, rustfmt; mingw-w64 GCC 13 posix, Node 22, python3 + numpy from the system (numpy added to APT for the simulators) |
|
||||
| sccache | `/usr/local/bin/sccache` (the build user's binary copied; /home/build is 750), config `/home/runner/.config/sccache/config` with `rw_mode = "READ_ONLY"` on /srv/sccache, own server port 4227. Shown: a job-shaped `cargo test --release` of igneum-pow as `runner` (99 tests pass, 42 s cold) made 6 compile requests, 0 hits, 6 cache WRITE ERRORS (the refusal, as wanted), and /srv/sccache stayed at 5,880,836 KB |
|
||||
| Jobs | `.env`: RUSTC_WRAPPER, SCCACHE_CONF, SCCACHE_SERVER_PORT 4227, CARGO_INCREMENTAL 0, CARGO_BUILD_JOBS 48 (half the box); `.path`: the runner's cargo bin, /usr/local/bin, /usr/bin, /bin. A CI job takes NO build slot today (ci-self-hosted.md, open row) |
|
||||
| Ephemeral | no. `--ephemeral` is for autoscaled fleets that register a fresh runner per job; one standing runner on a private repository keeps its registration and cleans `_work` per job (approximate: GitHub's docs host answered 404 to both fetches tonight, so this is the rule as remembered, labelled so) |
|
||||
| Registration | `infra/build-server/runner/register.sh`: gh as igneum-labs (fails on any other active account, checks the login is igneum-labs), `POST repos/igneum-network/igneum/actions/runners/registration-token`, the token as the first stdin line to `provision.sh` on the box (never an argument, never a file, never logged; the output is filtered for it as a belt). `--status` lists the repository's runners and the unit |
|
||||
| Idempotent | provision.sh runs 3 and 4 after the registration: `runner: ok`, no restart (`ActiveEnterTimestamp` unchanged). Run 2 had said `changed` because GitHub's `svc.sh install` runs `env.sh`, which rewrites `.env` and `.path`; the step now writes them AFTER the install |
|
||||
| Workflows | NOT changed (the shipper owns them tonight). The proposed diff and the fallback (repository variable `IGNEUM_CI_RUNNER`; GitHub has no "else" in `runs-on`) are in `docs/plans/ci-self-hosted.md`. `windows.yml` cannot move to a Linux box (MSVC, WebView2, Inno Setup, PowerShell 5.1) |
|
||||
|
||||
Consequences: ci.yml's `pow` and `sims` jobs would run on a pinned 1.99.0 (GitHub's `ubuntu-latest` ships whatever stable it has), with 48 jobs; GitHub-hosted minutes on a private repository are the thing saved. A CI job on the box reads only what the checkout gives it; the mirrors and `/srv/builds` belong to `build` and are not readable by `runner` (git's safe.directory is set for the runner so a future job may clone a mirror read-only if main wants it).
|
||||
|
||||
### 7.0 The slots ruling (main, 20:3x UK; DONE 19:28Z, live on the box)
|
||||
|
||||
| Change | Where | Shown by |
|
||||
|---|---|---|
|
||||
| 2 build slots (`/srv/builds/_locks/slots` = 2) | provision.sh `SLOTS` default 2, applied 19:28:18Z | `dirs: changed (... slots=2)` |
|
||||
| CARGO_BUILD_JOBS 90 when a build holds the only taken slot, 45 when it sees the other slot held (one second of settling after taking the slot, then a `flock -n` probe of the other file); a `-j` on the cargo line wins | remote-run.sh; tools/build-remote.sh and tools/cross-remote.sh pass `-j` only when `--jobs` is given | `remote-run.sh --self-test-slots` on the box: two concurrent fake builds get 45 each, a lone one 90 |
|
||||
| A measurement (`BR_MEASURE=1`) takes the `measure` file exclusively; builds hold it shared for their whole run, so a measure waits for the running builds and blocks new ones, as with-lock.sh's `measure` on the Mac | remote-run.sh; `infra/build-server/prover/cpu-trial.sh` is its first user | the self-test: a measure blocks a build, a build blocks a measure; JSONL carries `"jobs"` and `"measure"` |
|
||||
| Lock files opened in APPEND mode | remote-run.sh | the first version's `exec {fd}>build-k` truncated a BUSY slot's holder line each time another build probed it (the dashboard read empty lines for held slots); the self-test's case 5 keeps a holder line through a probe. The OLD script under the same cases: `JOBS=none JOBS=none`, FAIL (the known-failed run, 19:25Z) |
|
||||
| An environment IGNEUM_BUILD_SLOTS_DIR or IGNEUM_BUILD_LOG_DIR wins over the profile | remote-run.sh | the first self-test run let the profile reset the scratch dir and took the box's REAL slot for 7 s (19:24Z, box idle) |
|
||||
|
||||
Open: a worktree whose remote-run.sh predates this keeps the old behaviour until it has master with it (the script is piped from each Mac worktree per build), so until every agent rebases, a build from an old worktree still asks `-j 90` beside a new one at 45. The build-server agent was told at 19:28Z.
|
||||
|
||||
### 7.2 The night battery (DONE 19:42Z installed, dry run 19:45 to 19:49Z)
|
||||
|
||||
`infra/build-server/night/night-battery.sh`, run by `igneum-night-battery.timer` at 02:00 Europe/London (the box's clock is
|
||||
Europe/Berlin, so the unit names the zone: next run Wed 2026-10-07 03:00 CEST = 02:00 BST; not Persistent, a missed night is
|
||||
not run by day) through `igneum-night-battery.service` (User build, Nice 19, idle IO, 8 h limit), whose ExecStart is
|
||||
`remote-run.sh` with the battery as BR_CMD, so ONE build slot spans the whole invocation and one JSONL line records it.
|
||||
Installed by provision.sh `step_night` from the mirror at `NIGHT_REF` (box-work tonight, master once merged); the battery
|
||||
re-execs itself from master's checkout at run time. Every row: `cargo test --release --no-fail-fast` per crate (the repo's
|
||||
five crates and the proving workspace's host, core and export; every fork workspace member, kaspad with `igneum-pow`),
|
||||
igneum-pow's two fuzz tests at 2,000 programs (10x), the three simulators in full, the fast-time harnesses
|
||||
(`tools/finality-attacks/run.mjs --fast-time`, `tools/harness/run.mjs s3 s4 --fast-time --no-bench-log`,
|
||||
`tools/exec-sync/reorg.mjs`) on igneumd, igneum-miner, igneum-harness-sim and igneum-p2p-probe built into the night
|
||||
checkout's `target-integration`, clippy per crate dir, `cargo audit` per Cargo.lock; then `docs/benchmarks/night/<date>.md`
|
||||
(pass/fail table, "new since last night" against the newest earlier report, commits moved), committed as igneum-labs on
|
||||
branch `night-battery` (rebuilt on master each night, earlier unmerged reports carried over) and force-pushed to
|
||||
`/srv/igneum.git` only. Main merges: `git fetch build night-battery` from the main checkout. The fork branch defaults to the
|
||||
newest `release-*-node` on the mirror (tonight release-0.3.15-node 713ef876).
|
||||
|
||||
Dry run (`NIGHT_SUBSET=1`, the second slot while the repro held the first, so CARGO_BUILD_JOBS 45): **3 min 54 s** wall,
|
||||
10 pass, 1 FAIL, 1 skip; report `docs/benchmarks/night/2026-10-06-dryrun.md` on the mirror's night-battery branch (fbb72e5).
|
||||
|
||||
| Row | Result | Time | Detail |
|
||||
|---|---|---|---|
|
||||
| suite igneum-pow | pass | 51 s | 99 passed |
|
||||
| suite fork/kaspa-pow | pass | 1 min 04 s | 7 passed |
|
||||
| suite fork/igneum-miner | pass | 49 s | 18 passed |
|
||||
| fuzz igneum-pow x200 | pass | 11 s | 200 mx8 programs and 200 scratch programs, 800 units each |
|
||||
| sim finality_sim.py, finality_v2.py --quick, difficulty/sim.py --quick | pass | 3 s, 41 s, 5 s | 249, 117, 8 table lines |
|
||||
| clippy igneum-pow | pass | 3 s | 28 warnings |
|
||||
| audit igneum-pow | pass | 3 s | 0 vulnerabilities |
|
||||
| audit vendor/igneum-node | **FAIL** | 2 s | 22 advisories in the fork's lock file: h2 (RUSTSEC-2026-0258, unbounded empty DATA frames), quinn-proto (2026-0185, remote memory exhaustion), rustls (2026-0285, TLS 1.3 handshake across encryption levels), ruint (2026-0220), crossbeam-epoch, anyhow (2026-0190), event-listener, faster-hex (2026-0306, AVX2 read past src), lru (2026-0253), tracing-subscriber (2025-0055), chacha20, spin; 17 unmaintained-crate warnings (async-std discontinued, atty, bincode, derivative, instant, mach, paste, proc-macro-error, rustls-pemfile) |
|
||||
| harness | skip | | not in the subset; its first run is the 02:00 battery, so the first full report will show whether the Node harnesses run on Linux unchanged (c4, fud and v3.mjs default IGNEUM_NODE_ROOT to /Users/joshm/Projects/igneum/; the battery sets it) |
|
||||
|
||||
What the FAIL means and what follows: every node binary shipped so far (and the 0.3.15 one tonight) links h2, quinn-proto and
|
||||
rustls at versions with published advisories; h2 and quinn-proto are in the gRPC and QUIC paths a peer can reach, so these are
|
||||
the remote ones. The fix is a dependency bump in the fork (`cargo update -p h2 -p quinn-proto -p rustls -p ruint -p
|
||||
crossbeam-epoch -p anyhow -p event-listener -p faster-hex -p lru -p tracing-subscriber`, then the suites), a consensus
|
||||
engineer's hour on a quiet branch, and the row goes green by itself the next night. Until then the row stays FAIL every night
|
||||
and "new since last night" stays quiet about it. The unmaintained-crate warnings are upstream rusty-kaspa's and do not fail
|
||||
the row.
|
||||
|
||||
Expected full-run time (not measured yet): the three suites above compile the fork's test targets once (about 1 min each for
|
||||
the first crates, seconds after), so 75 fork members plus the repo crates are estimated at 40 to 70 min; the full sims about
|
||||
10 min (finality_v2.py is 5 min on the Mac); the harnesses 15 to 30 min; clippy and audit under 10 min. Under 2 h, inside
|
||||
the 8 h limit; the first report at 02:00 BST writes the real number.
|
||||
|
||||
### 7.3 Reproducible builds (DONE 19:52Z; A vs B MATCH on all four, DIFFER against the shipped bytes, both explained)
|
||||
|
||||
`tools/repro/rebuild-release.sh <version>` (the Mac) reads the pins from `docs/plans/release-<v>.md` (the heading
|
||||
"(node <sha>, app <sha>)" and the bold hashes of the Linux and Windows rows), makes sure both commits are on the mirrors, and
|
||||
runs `infra/build-server/repro/rebuild-on-box.sh` on the box: a clean clone of the fork at the node commit on a branch under
|
||||
a clean clone of the repo at the app commit, two clean passes per target in ONE target path each, no sccache, under build
|
||||
slots through remote-run.sh, `SOURCE_DATE_EPOCH` = the node commit's time, `TZ=UTC`; the shipped hashes come token-free from
|
||||
the public downloads (the HiveOS tarball for the Linux pair; the installer for the exes, when innoextract can open it) with
|
||||
the plan's hashes as the fallback; the evidence goes to `docs/evidence/reproduced/<version>.md`. The 0.3.14 run (node
|
||||
4c6b129d, app a90f6a5): four passes of 70 to 78 s, whole run 5 min 06 s.
|
||||
|
||||
| Artefact | A vs shipped | A vs B | Why the shipped bytes differ (read off the binaries) |
|
||||
|---|---|---|---|
|
||||
| igneumd (box 03f35e05..., 49,600,096 B) | DIFFER | **MATCH** | shipped 934f393c... was the Mac's zig build for glibc 2.36; the box's needs GLIBC_2.39 (native clang and lld). Commit string 4c6b129d in the box's: 1 hit |
|
||||
| igneum-miner (box 900c1f0b..., 9,842,168 B) | DIFFER | **MATCH** | the same toolchain difference |
|
||||
| igneumd.exe (box 166e604e..., 51,758,592 B) | DIFFER | **MATCH** | shipped 44fa74c0... came from the Mac's Homebrew mingw at 16:52Z, before the --no-insert-timestamp fix (17:48Z) and from a worktree (the empty-commit class); the box's is Ubuntu GCC 13 posix with a zero PE timestamp and the commit string (1 hit). innoextract 1.9 cannot open the Inno Setup 6 installer (setup loader revision 2), so the shipped exe's own header was not read |
|
||||
| igneum-miner.exe (box fefd266c..., 10,994,688 B) | DIFFER | **MATCH** | the same |
|
||||
|
||||
Two non-determinisms found on the way, both in the SHIPPED builds too (first run 19:43Z, passes A and B differed on all four):
|
||||
|
||||
| Class | Fact | Fix |
|
||||
|---|---|---|
|
||||
| OUT_DIR path in the binary | prost's generated `protowire.rs` (kaspa-grpc-core, kaspa-p2p-lib) embeds its OUT_DIR path; a pass in a target dir of another NAME differs (igneum-miner matched byte for byte once the path was the same) | one target path per target in the repro; for cross-machine identity a `--remap-path-prefix` of the target dir and the home (not done: the Mac and the box differ in every path anyway) |
|
||||
| Build clock in the binary | libmimalloc-sys compiles mimalloc's C with `__DATE__` and `__TIME__` ("Oct 6 2026", "21:38:15" sat in libmimalloc.a, next to the mimalloc option names); two builds a minute apart differ | `SOURCE_DATE_EPOCH` exported for every pass (GCC and clang take the date and time from it); PROPOSED for build-remote.sh, cross-remote.sh, cross-build.sh and the PC job: export it from the commit time so two builds of one commit give one hash. The earlier "byte-identical across three builds" on the box was under sccache, which returns the first build's object and hides this class |
|
||||
|
||||
0.3.15 as well (run 19:56 to 20:00Z, the moment it reached dl/public; node 713ef876, app 563485b; four clean passes of 63 to
|
||||
76 s): A vs B **MATCH on all four** again (igneumd 1f1b6eee..., igneum-miner a34e0a56..., igneumd.exe 9b377455...,
|
||||
igneum-miner.exe 65b30edd...). Against the shipped Linux pair in `igneum-hive-0.3.15.tar.gz` (igneumd 1e51bfb6...,
|
||||
igneum-miner c5b48910...): DIFFER, and the binaries say why: the shipped pair needs GLIBC_2.34 and embeds
|
||||
`/Users/joshm/.cargo/registry` and the clock string 11:05:57, so the HiveOS package carries the Mac's zig build, not the
|
||||
box's 06211d55... of 19:00Z (the box build needs GLIBC_2.39, which HiveOS cannot run; the zig lane is right for that
|
||||
package). The Windows exes have no shipped hash yet (the public installer is still 0.3.14). `docs/evidence/reproduced/0.3.15.md`.
|
||||
|
||||
Re-run 20:13 to 20:22Z with sccache REALLY off (the build-server agent's finding: an empty `RUSTC_WRAPPER=` is read by cargo as
|
||||
unset and falls back to the box's config, so the first runs' passes could take hits; now `RUSTC_WRAPPER=/usr/bin/env`, a
|
||||
pass-through, and `SOURCE_DATE_EPOCH` = the author time of lib.sh `bs_sde`): both versions, all four artefacts, A vs B
|
||||
**MATCH** with the same hashes as above (0.3.14: 03f35e05, 900c1f0b, 166e604e, fefd266c; 0.3.15: 1f1b6eee, a34e0a56,
|
||||
9b377455, 65b30edd); passes of 73 to 177 s with the two repros side by side on the two slots. The MATCH rows stand on their own.
|
||||
|
||||
What it means: the box is deterministic for a given commit and path, so a release built on it can be checked by anyone with the
|
||||
same toolchain by rebuilding and comparing; the shipped 0.3.14 and 0.3.15 Linux bytes came from the Mac's zig lane with a
|
||||
build clock inside and cannot be reproduced anywhere, and the Windows 0.3.14 exes carried a PE timestamp. The reason to ship
|
||||
every target from the box from 0.3.16 (R2) is this table, with SOURCE_DATE_EPOCH exported in every build script; for HiveOS
|
||||
(glibc 2.36 and under) the box needs zig + cargo-zigbuild first (section 6, row 1), or the package keeps the Mac's build and
|
||||
stays unreproducible until then. Open: a `--reuse` re-report of 0.3.15 once its Windows installer is public, and
|
||||
`rebuild-release.sh 0.3.16` the moment it ships.
|
||||
|
||||
### 7.4 The CPU prover trial (DONE 19:30Z; verdict: the box is NOT a prover)
|
||||
|
||||
`infra/build-server/prover/cpu-trial.sh` on the box under the measure hold (builds excluded), `igneum-prove-host` from master
|
||||
7483fb37 (the 0.3.15 prover pair, sha256 71bc2438...; its `cuda` feature changes nothing under `SP1_PROVER=cpu`), fixture
|
||||
`proving/fixtures/block-56-transfers.json` (the v0 block: 3 transfers, 600 pgas, one shard, 556,369 SP1 cycles), `--mode shard
|
||||
--shard 0`, `RAYON_NUM_THREADS=96`, nice 19, box otherwise idle (load 2.6 at start). Log and results JSON:
|
||||
`/srv/builds/_log/prover-trial/trial-20261006T192824Z.{log,json,txt}`; JSONL line kind `measure`.
|
||||
|
||||
| Stage | Box, 96 threads (EPYC 9454P) | Mac, same statement class (bench-log) |
|
||||
|---|---|---|
|
||||
| setup (prover client, shard and aggregator keys) | 14.4 s (client 12.6, keys 1.8) | 9.3 s per invocation in the v0 loop (4 Oct, "nearly all SP1 setup") |
|
||||
| execute | 0.28 s, 556,369 cycles, 927 cycles per pgas | 0.19 s for block 78 (3 Oct) |
|
||||
| core proof | **34.2 s**, 7,317,561 B, verify 0.34 s | 83 s for the 200-pgas shard on a Mac at load 40 (4 Oct); 71.7 s is main's Mac figure for this fixture (its stage not recorded here, labelled approximate) |
|
||||
| compressed proof (what a record carries) | **85.9 s**, 1,272,897 B, verify 0.07 s | 272 s for the 200-pgas shard on the loaded Mac (4 Oct); 61 s for the smallest shard in the 3-node v0 loop |
|
||||
| whole run, wall | 136.9 s | |
|
||||
| peak RSS | 28.2 GB (VmHWM) | |
|
||||
| CPU use | 64 of 96 threads busy on average (6,408 percent in `ps`) | |
|
||||
|
||||
What the numbers mean, per tier, and what follows:
|
||||
|
||||
| Number | Means | Done or proposed |
|
||||
|---|---|---|
|
||||
| core 34.2 s under 60 s, compressed 85.9 s over it | the proof a record carries is the compressed one, so the shard that matters takes 120 s of proving on 96 CPU threads for the SMALLEST shard the chain has (600 pgas, 0.56 M cycles); a shard at `S_p` is 60 M cycles (bench-log 4 Oct), about 100x, so hours per shard on this CPU against the 60 s proof lag the litepaper states | the 60 s test of the ask is NOT met on the stage that counts; NO standing CPU prover unit is written, nothing joins the devnet from the box (R6 holds: the box is never a node host for the live devnet) |
|
||||
| 28.2 GB peak for the smallest shard | a CPU prover needs 32 GB of RAM for a toy shard; every home tier (8, 12, 16, 24 or 32 GB CARDS, 16 to 64 GB of RAM) is out of CPU proving, and the rented 4090 boxes' CPUs are not a fallback either | the app keeps "proving on the CPU (slow)" as a correctness lane only; the prover tiers are the real cards (docs/analysis, 6 Oct rented-card measurement) |
|
||||
| 64 of 96 threads busy | SP1's CPU prover does not scale to the whole box; a second trial with `RAYON_NUM_THREADS=48` would show whether half the box proves as fast (then two shards side by side) | not run tonight (one slot of the box's evening); the script takes `--threads` |
|
||||
| 14.4 s setup per invocation | the same per-process cost the Mac pays; a resident prover would pay it once | already the design of the app's prover loop |
|
||||
|
||||
The box stays a build and test machine. If main wants a CPU prover anyway for coverage (a prover that is always on, never fast),
|
||||
the shape is a systemd unit as `build` with `SP1_PROVER=cpu`, a throwaway devnet key (never the OTA key, never a hand's key),
|
||||
`--threads 48`, Nice 19 and the measure hold taken for the whole run, which would exclude builds for minutes at a time: that
|
||||
is why it is not written.
|
||||
|
||||
## 8. The capacity layer (6 October 2026, the project lead: "get the builder spun up to capacity")
|
||||
|
||||
The box sits idle most of the minute between builds. `infra/build-server/capacity/` is a background workload layer that
|
||||
uses the idle cores and yields to builds. It runs as `igneum-capacity.service` (user `build`, `Nice=19`, `chrt -i 0`
|
||||
SCHED_IDLE, `IOSchedulingClass=idle`, `CPUQuota=8800%` so 8 of the 96 threads are always free) and is a controller
|
||||
(`run.sh`) over a queue of jobs in `jobs/`. All work lives under `/srv/capacity`; the layer takes NO build slot and
|
||||
writes NOTHING under `/srv/builds/<worktree>`.
|
||||
|
||||
### Rules (the layer obeys these; they are why a build never waits on it)
|
||||
|
||||
| Rule | How |
|
||||
|---|---|
|
||||
| Background work never takes a build slot | the jobs run cargo directly in `/srv/capacity` and never call `remote-run.sh`; the controller's `cap_build_active` only READS `/srv/builds/_locks` with `flock -n` |
|
||||
| It pauses whenever a build slot is taken or the measure hold exists | `run.sh` polls `/srv/builds/_locks` every 5 s; it `SIGSTOP`s the running job's whole process group the instant any `build-<k>` or `measure` is held and `SIGCONT`s it when they clear; it does not even start a new slice while a build runs |
|
||||
| It never touches `/srv/builds/<worktree>` trees | its own checkout is `/srv/capacity/src` (repo) and `/srv/capacity/src/vendor/igneum-node` (fork), its targets are under `/srv/capacity`; the only `/srv/builds` access is a READ of an existing node binary and a READ of the lock files |
|
||||
| It is killed by the night battery's start and restarted after | `igneum-night-battery.service` has `ExecStartPre=+-systemctl stop igneum-capacity.service` and `ExecStopPost=+-systemctl start igneum-capacity.service` (the `+` runs as root; the `-` never fails the battery) |
|
||||
|
||||
### The jobs, in priority order (`CAP_SEQUENCE` gives the earlier ones more turns)
|
||||
|
||||
| # | Job | What | Out | Dry-run / smoke |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `pow-fuzz` | continuous `igneum-pow` mixer and scratch fuzz; `IGNEUM_FUZZ_SEED_BASE` advances from a cursor so every slice walks fresh programs; counts programs and units, saves any mismatch with its seed base | `/srv/capacity/out/pow-fuzz/<date>/` | `--dry-run` / `--smoke` (N 100, 10 min) |
|
||||
| 2 | `sync-fuzz` | the sync-request gate for the 28 unwrap sites (`docs/analysis/horizon/consensus-security.md` s5): a throwaway pruned `igneumd` on a simnet datadir on the box (net `igneum-devnet-315`, loopback only, NEVER the live devnet), fed random, boundary and below-retention locator/header/antipast/IBD/pruning-point requests by `igneum-p2p-probe sync-fuzz`; a gRPC alive check every 25 requests; any panic saved with the trace | `/srv/capacity/out/sync-fuzz/<date>/` | `--dry-run` (builds one request of each kind) / `--smoke` (6 min of requests) |
|
||||
| 3 | `sim-sweeps` | GHOSTDAG (`ghostdag_sim.py --seed-base`), finality (`finality_horizon.py`, `finality_v2.py --quick`) and difficulty attacks (`attacks.py --seed-base`) across seeds 1 to 1,000; CSV appended; a daily note of any bound that moved | `/srv/capacity/out/sim-sweeps/<date>/sweeps.csv` | `--dry-run` / `--smoke` (one seed, quick) |
|
||||
| 4 | `model-sweeps` | the N-ladder and chip model (`sim/horizon/algorithm/model.py`) over every section, cached by the file's content hash | `/srv/capacity/out/model-sweeps/<date>/` | `--dry-run` / `--smoke` |
|
||||
| 5 | `clippy-audit` | `cargo clippy` and `cargo audit` on every branch pushed to the box repo mirror in the last day, in its own checkout `/srv/capacity/clippy`, recorded per branch and commit so a slice only picks up new pushes | `/srv/capacity/out/clippy-audit/<date>/<branch>/` | `--dry-run` / `--smoke` (one crate, newest branch) |
|
||||
|
||||
Each job is a script in `jobs/` with a `--dry-run` mode (no build, no node, no run) and a `--smoke` mode (a ~10 minute
|
||||
bounded run). Every job writes one line per slice into `/srv/workers/capacity.json` (`summary.mjs`, atomic), which the
|
||||
worker dashboard's collector (`tools/workers/collect.mjs`) reads into `doc.background`; the page (`tools/workers/page`)
|
||||
shows a "Background" lane from it.
|
||||
|
||||
### Install
|
||||
|
||||
`infra/build-server/capacity/install.sh` (idempotent): copies the scripts and the unit, pushes the `capacity-probe`
|
||||
fork branch (the `sync-fuzz` subcommand) and the `box-capacity` repo branch to the box mirrors, and enables the
|
||||
service. The layer tracks the repo branch `master`; until this work merges, the mirror's `master` lacks the capacity
|
||||
tree, so install writes a drop-in pinning `CAP_REPO_BRANCH=box-capacity` and removes it once `master` carries
|
||||
`infra/build-server/capacity/run.sh` (self-healing after the merge). `--no-start` enables without starting;
|
||||
`--smoke <job>` installs then runs one job's 10-minute smoke and prints the summary.
|
||||
|
|
|
|||
75
docs/plans/ci-self-hosted.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# CI on the box: the self-hosted runner and the workflow change (proposal, 6 October 2026)
|
||||
|
||||
The runner `igneum-build-1` (labels `self-hosted, linux, x64, igneum-build-1`) is installed by `infra/build-server/provision.sh`
|
||||
step_runner and registered by `infra/build-server/runner/register.sh` (docs/plans/build-server.md section 7). The workflows are
|
||||
NOT changed here: the shipper owns `.github/workflows` tonight. This is the proposed diff for main.
|
||||
|
||||
## 1. The shape: one repository variable decides, GitHub-hosted is the fallback
|
||||
|
||||
GitHub has no "try this runner, else that one" in `runs-on`: a list of labels means ALL of them must match one runner, so
|
||||
`[self-hosted, igneum-build-1, ubuntu-latest]` would never schedule. The fallback is therefore a repository variable read in
|
||||
the expression. `IGNEUM_CI_RUNNER` = `box` sends the job to the box; unset or anything else keeps `ubuntu-latest`. Flipping it
|
||||
back is one click in Settings > Secrets and variables > Actions > Variables (or `gh variable set IGNEUM_CI_RUNNER --body box`
|
||||
and `gh variable delete IGNEUM_CI_RUNNER` as igneum-labs), with no commit and no queue lost: a job already queued for the box
|
||||
stays queued; the next push goes to GitHub's machines.
|
||||
|
||||
## 2. ci.yml (the two jobs that compile or compute; the `site` job stays on GitHub's machines)
|
||||
|
||||
```diff
|
||||
jobs:
|
||||
pow:
|
||||
name: igneum-pow tests, igneum-census build
|
||||
- runs-on: ubuntu-latest
|
||||
+ runs-on: ${{ vars.IGNEUM_CI_RUNNER == 'box' && fromJSON('["self-hosted", "linux", "x64", "igneum-build-1"]') || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: toolchain
|
||||
run: rustc --version && cargo --version
|
||||
@@
|
||||
sims:
|
||||
name: simulators, quick modes
|
||||
- runs-on: ubuntu-latest
|
||||
+ runs-on: ${{ vars.IGNEUM_CI_RUNNER == 'box' && fromJSON('["self-hosted", "linux", "x64", "igneum-build-1"]') || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
+ if: vars.IGNEUM_CI_RUNNER != 'box' # the box has python3 and numpy from provision.sh; setup-python would download a second Python
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- - run: python3 -m pip install --quiet numpy
|
||||
+ - run: python3 -m pip install --quiet numpy
|
||||
+ if: vars.IGNEUM_CI_RUNNER != 'box'
|
||||
```
|
||||
|
||||
What the box gives these two jobs: rustc 1.99.0 pinned (GitHub's `ubuntu-latest` carries whatever stable it ships; the box
|
||||
is the Mac's version, so CI compiles what the agents compile), sccache hits from the agents' cache (read-only), 48 cargo
|
||||
jobs. The `site` job is Node and shell checks and takes under a minute on GitHub's runners; moving it buys nothing and would
|
||||
put `tools/ci/public-api-check.mjs` (a live HTTPS check) behind the box's egress for no reason.
|
||||
|
||||
Why the toolchain line still runs: on the box `rustc --version` must print 1.99.0; a mismatch means provision.sh and the
|
||||
runner's rustup disagree (R5 in build-server.md) and the job should say so in its first step.
|
||||
|
||||
## 3. windows.yml: no change possible on this box
|
||||
|
||||
Every job of `windows.yml` runs on `windows-latest` for a reason the box cannot answer: the engine builds on the MSVC
|
||||
target, the window host needs the Windows SDK and WebView2, the installer needs Inno Setup, the smoke run executes the exes
|
||||
and the launcher under Windows PowerShell 5.1. A Linux runner has none of that. The only self-hosted option for this
|
||||
workflow is a Windows runner on PC 1 or PC 2 (`actions/runner` for Windows under a service account), which conflicts with
|
||||
the rule that the PCs keep only GPU and Windows-runtime JOBS through the signed job system, and is not proposed tonight.
|
||||
|
||||
What the box already does for Windows is upstream of this workflow: `tools/cross-remote.sh` builds `igneumd.exe` and
|
||||
`igneum-miner.exe` (the payload inputs) in 1 min 44 s, and the night battery rebuilds them for the reproducibility record.
|
||||
|
||||
## 4. What to check after the flip (main, the first run on the box)
|
||||
|
||||
| Check | Where | Pass |
|
||||
|---|---|---|
|
||||
| the job landed on the box | the run's "Set up job" log says `Runner name: 'igneum-build-1'` | yes |
|
||||
| the toolchain | the `toolchain` step prints `rustc 1.99.0` | yes |
|
||||
| sccache hits | add `sccache --show-stats` as a step once, or read `/srv/sccache` size before and after: the runner's config is READ_ONLY, so the size must NOT change | size unchanged |
|
||||
| the agents were not starved | `/srv/builds/_log/builds.jsonl` `secs` of the builds during the run against the same crate's earlier lines | within the usual spread |
|
||||
| the fallback | `gh variable delete IGNEUM_CI_RUNNER`, push a no-op commit: the job runs on `ubuntu-latest` again | yes |
|
||||
|
||||
Open: a CI job on the box does not take a build slot (`/srv/builds/_locks/build-<k>`), it runs at Nice 10 with 48 jobs; if a
|
||||
CI job ever delays a release build visibly, the fix is a step at the top of the job that takes a slot through
|
||||
`infra/build-server/remote-run.sh`'s flock, the same file the agents use.
|
||||
|
|
@ -29,7 +29,7 @@ prompt) for both knobs; off, it measures only.
|
|||
| Vendor | Power limit | Core clock cap | Memory clock | How | Rights |
|
||||
|---|---|---|---|---|---|
|
||||
| NVIDIA | `nvidia-smi -pl <W>`, percent of the default, inside `power.min_limit` and `power.max_limit` | `nvidia-smi -lgc 0,<MHz>`, percent of `clocks.max.gr`; `-rgc` = unlocked | never touched (`-lmc` is not used); read back as `clocks.mem` | directly when the engine is elevated, else the one-prompt helper (`<seq> pl <W>`, `<seq> lgc <MHz>`, `<seq> rgc` in `sweep/cmd.txt`) | administrator, so only with Power control on |
|
||||
| AMD | `igneum-gpu-telemetry --card N --set-plimit <offset>` (0 = default, -20 = 80%), inside the `tune` line's `plimit_range` (PC 1's 9070 XT: -30 to 10, so 70% is the floor) | `--set-gmax` only when the `tune` line's `gmax_range` is absolute MHz (floor 0 or above); on RDNA 4 the range is an offset from stock (-500 to 1000 on PC 1) and the clock knob stays closed until the stock clock is known; `--reset` for the default point | not settable through ADLX on RDNA 4; read back as `mclk_mhz`, and a step whose mean memory clock falls under 95% of the baseline's is marked and cannot win | the helper, one process per request, exit 0 and a `tune ... ok` line | none on Windows (ADLX manual tuning); root on Linux, so measure only there |
|
||||
| AMD | `igneum-gpu-telemetry --card N --set-plimit <offset>` (0 = default, -20 = 80%), inside the `tune` line's `plimit_range` (PC 1's 9070 XT: -30 to 10, so 70% is the floor). RULE (run 6, 6 October 2026): ADLX reports the limit as an OFFSET, never watts, so an AMD step is applied on the helper's acknowledgement (`Run::applied`: a card with no limit readback confirms on `acked`), and the draw comes from the telemetry stream (`sample_telemetry`), never from a limit readback; `limit_w` is 0 for every AMD card by design | `--set-gmax` only when the `tune` line's `gmax_range` is absolute MHz (floor 0 or above); on RDNA 4 the range is an offset from stock (-500 to 1000 on PC 1) and the clock knob stays closed until the stock clock is known; `--reset` for the default point | not settable through ADLX on RDNA 4; read back as `mclk_mhz`, and a step whose mean memory clock falls under 95% of the baseline's is marked and cannot win | the helper, one process per request, exit 0 and a `tune ... ok` line | none on Windows (ADLX manual tuning); root on Linux, so measure only there |
|
||||
| Apple | none | none | none | measure only | none |
|
||||
|
||||
Vendor limits are never exceeded and the floor is never undercut: the plan clamps every point (`Limits::clamp_clock`,
|
||||
|
|
@ -161,6 +161,172 @@ maximum, 14,001 MHz memory; 9070 XT present on bus 98 with OFFSET ranges `gmax_r
|
|||
10`). The offset finding changed the AMD mapping (054e041): an offset clock range closes the clock knob and the power
|
||||
ladder runs on a percent scale bounded by `plimit_range`. The re-run follows the 0.3.11 rollout.
|
||||
|
||||
## 6a. A card never shows 0 MH/s without a reason word (the project lead, 6 October 2026, watching PC 1 during run 5)
|
||||
|
||||
Rule: the Mine page's rate column is blank, never "0", when a card is not mining, and the row's word says why:
|
||||
`mining`, `tuning: step k of n · measuring W W` (the live rate stays in the rate column), `held for a remote job:
|
||||
<title>`, `paused`, `waiting for the node`, `restart in N s`, `starting`, `ready`, `stopped after repeated faults`,
|
||||
`failed`, `off`, `removed`, `not usable (<problem>)`. Checked against `View.cardRow`: every state the engine can set
|
||||
has a word; `held` and `tuning` were the two that read "off" before (a job's `--stop-miners` left `state = off`).
|
||||
During a tune the big button reads "Tuning, mining again in about N min" (disabled) and the strip carries one line
|
||||
("Tuning <card>: the full tune, step k of n. Mining again in about N min."). A tune run by a job beside the installed
|
||||
app reaches the installed app's rows through `POST /api/tune-progress` (the playbook forwards the measurement
|
||||
engine's `TUNE progress` lines every 10 s and its `TUNE chosen` line as `done`): the posted state is the step, the
|
||||
live MH/s and W, the seconds left and the plan; a POST of state, never a quit, pause or resume. At the end the row
|
||||
reads `Tuned: <MH/s> at <W> W (<MH/W>)` with the point and the plan. Tests: `ui/tune-line.test.mjs` (tuning, held,
|
||||
tuned rows; the button; the strip line).
|
||||
|
||||
## 6b. Ember 2: the clocks, the hill-climb, the goal (6 October 2026)
|
||||
|
||||
Why: run 5 measured the 5090's power ladder flat (310 to 314 W under every cap from 575 to 400 W, 0.41 MH/W), so
|
||||
on this memory-latency-bound hash the power cap is a safety, not a lever; the pair that matters is the memory
|
||||
clock UP and the core clock DOWN. Ember 2 adds both and replaces the fixed ladder with a hill-climb.
|
||||
|
||||
| Piece | What | Where |
|
||||
|---|---|---|
|
||||
| The memory knob | NVIDIA: `nvidia-smi -lmc <m>,<m>` locks the memory clock to one value, `-rmc` resets; the range is the driver's own (`clocks.mem` under load = the floor, `clocks.max.mem` = the ceiling; PC 1's 5090: 13,801 to 14,001 MHz); directly from an elevated engine or through the Power Helper's `lmc`/`rmc` verbs. AMD: ADLX on RDNA 4 exposes no memory setter through the helper's path, so an AMD card climbs on core and power only (the row says so). Linux NVIDIA: `-lmc` through pkexec as the cap is | `Point.mem_mhz`, `Limits.mem_default_mhz`, `Limits.mem_max_mhz`, `tune_apply`, `powertask::HelperCmd::MemClock`, `sweep::helper_script_*` |
|
||||
| The hill-climb | from the start point (the fleet prior for the model, else the card's point): each probe moves memory up by 5% of the range above the default (at least 25 MHz) or core down by 5% of the maximum (at least 25 MHz); a probe that improves the goal's score keeps that direction, else the climb turns to the other knob, then to both; 5 probes of 60 s (10 s settle) converge in under 10 minutes | `Plan::climb`, `Climb`, `Plan::climb_next` |
|
||||
| The stability guard | a rejected or mismatched hash during a probe marks it `faulted`; a faulted probe backs that knob off for good in this climb (memory never goes above, core never below, the last good point); the hash fingerprint is the worker's own self-test at every pack (a variant that is not bit-exact never serves), so a step that keeps mining with 0 mismatches is stable by the only test that matters | `Row::from_samples`, `climb_next`'s `refused_mem` / `refused_core`, docs/design/miner-tuning.md |
|
||||
| The goal | Settings > goal: `efficiency` (the most MH/W within 10% of the top rate), `balanced` (within 1%, lever 3's rule), `rate` (the fastest point; MH/W breaks ties); one goal for every card | `Goal`, `Settings.tune_goal`, `POST /api/tune/goal {goal, price_pence, climb}` |
|
||||
| The price | Settings > electricity, pence per kWh: the card's row reads the chosen point as £ a day (`watts × 24 / 1000 × price / 100`: 310 W at 28.5 p = £2.12) | `pounds_per_day`, `Settings.power_price_pence`, the UI |
|
||||
| The curve | every row of the last plan on the card (`tune_curve`: point, MH/s, W, MH/W, clocks, hottest reading, mark) so a manual tuner sees the points the climb measured | `CardState.tune_curve`, the card detail |
|
||||
| The fleet | the record's `steps` already carry the curve; the prior gains `mem_mhz` (the median memory clock, 10 MHz steps); a stranger's card of a known model starts its climb at the prior's point | `relay/lib/ember.mjs`, `ember::prior_of` |
|
||||
| Re-check | weekly (the manifest's period), after a driver major or program-class change, and when the card's temperature band changes (owed: the band trigger) | `tick_sweep` |
|
||||
|
||||
Tests: `ember::tests::the_climb_walks_memory_up_and_core_down_and_converges_in_five_probes` (a synthetic
|
||||
memory-bound card: every probe moves memory up or core down inside the range, the chosen point beats the start in
|
||||
five probes), `a_refused_probe_backs_that_knob_off_for_good`, `each_goal_picks_its_point` (and the £/day formula),
|
||||
`powertask::tests` (the `lmc`/`rmc` verbs reach nvidia-smi as `-lmc m,m` and `-rmc`), `sweep::tests` (the helper
|
||||
scripts carry them), `relay/test/ember.test.mjs` (climb records aggregate into a prior with the memory clock).
|
||||
|
||||
Owed: the measured comparison on PC 1 (one climb per card against run 5's ladder rows, no prompt through the
|
||||
Power Helper), the temperature-band trigger, NVAPI/nvidia-settings for cards whose driver refuses `-lmc`.
|
||||
|
||||
## 7a. One administrator approval, ever
|
||||
|
||||
Threat note, `reregister` (6 October 2026): the verb takes no path. The helper, already elevated, picks the installed
|
||||
exe from the two install folders itself (`powertask::install_candidates`), so the most a writer of `cmd.txt` can do
|
||||
is point the task back at the installed app. The exposure that remains is the one every per-user install with a
|
||||
highest-run task has: the install folder is the user's own, so a process running as the user can replace the exe the
|
||||
task runs. The installer's code signature and the OTA's hash check are the answer to that, not the task. (0.3.13; the project lead, 6 October 2026, 11:50 UTC)
|
||||
|
||||
What 0.3.12 does: Power control on raises one prompt and sets every cap in that step; every later cap (an app start, a
|
||||
reboot, a slider move) and every tune's helper is another elevated launch, so another prompt. Not "once, ever".
|
||||
|
||||
What `src/powertask.rs` does: the first approval's elevated step also registers a per-user Windows scheduled task,
|
||||
`Igneum Power Helper` (principal = the signed-in user, interactive logon, RunLevel Highest, no trigger, hidden, one
|
||||
hour limit, new starts ignored while one runs), whose action is the app's own exe in the install folder with
|
||||
`--power-helper`. A task the user owns is started by the user's unelevated engine with `Start-ScheduledTask`, no
|
||||
prompt, and runs elevated. Every later cap and every tune's helper starts the task and writes the command file
|
||||
`<app data>/app/sweep/cmd.txt` (`<seq> dev <n>`, `<seq> pl <W>`, `<seq> lgc <MHz>`, `<seq> rgc`, `quit`). The task
|
||||
survives app restarts, updates (the per-user installer replaces the exe in place; the task's action path is the
|
||||
install folder) and reboots. Power control off starts the task once and sends `remove`: the helper unregisters the
|
||||
task (elevated) and exits; nothing is left behind. Linux keeps pkexec per step; macOS has no cap.
|
||||
|
||||
Threat note: the helper runs only fixed verbs with digit-only arguments through `Command::new(nvidia-smi).args`
|
||||
(the driver's own path, never PATH, never a shell); a line that is anything else is ignored; the sequence must rise
|
||||
(a stale file runs nothing); an attacker running as the user gains the power limit and clock cap of the user's own
|
||||
NVIDIA cards inside the driver's ranges, which the same user could set with one approved prompt anyway; no file,
|
||||
process, registry key or other binary is reachable through it. Tests: `powertask::tests` (the parser refuses every
|
||||
non-digit or extra argument, the arguments reach nvidia-smi as a list, the registration is per-user, highest,
|
||||
trigger-less and quote-safe, a stale command file runs nothing).
|
||||
|
||||
### Run 6 (6 October 2026, 16:01Z, PC 1 on 0.3.13 with kit-6 = 564bdea, elevated, the project lead's one click)
|
||||
|
||||
The scheduled task registered inside the run ("helper registered=true"), but its action was the run's scratch copy
|
||||
(`igneum-tune-20261006-170105\bin\igneum-app.exe`): `task_exe` preferred the installed exe only for a path under
|
||||
`jobs\`. Fixed the same hour: the running exe stands only when it is itself an install candidate; and the helper
|
||||
gained a `reregister` verb (no path argument, so a writer of cmd.txt can never choose what runs elevated: the helper
|
||||
finds the install folder itself, re-registers, and logs the task's action read back). The scratch root stays until
|
||||
that read-back shows the installed path.
|
||||
|
||||
RTX 5090 (driver 617.14, class l128w0, no prior), full plan, every row ok, 65 C at most:
|
||||
|
||||
| Step | Clock cap | Power | Draw | Rate | MH/W |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 (before) | unlocked | 100% 575 W | 311.0 W | 127.90 | 0.411 |
|
||||
| 1 to 4 | unlocked | 90, 80, 70, 60% | 313 W | 127.9 | 0.408 to 0.409 (the cap never binds) |
|
||||
| 5 | 2781 MHz | 100% | 298.8 W | 127.89 | 0.428 |
|
||||
| 6 | 2472 MHz | 100% | 262.0 W | 127.86 | 0.488 |
|
||||
| 7 | 2163 MHz | 100% | 239.9 W | 127.82 | 0.533 |
|
||||
| 8 (chosen) | 1854 MHz | 100% | 226.8 W | 127.71 | 0.563 |
|
||||
|
||||
The per-user number: 84 W saved for 0.15% of rate, 37% more hashes per watt. The chosen clock is the ladder's floor
|
||||
(60% of 3,090), not the optimum: `CLOCK_STEPS_PCT` now ends 60, 50, 45 and `CLOCK_FLOOR_PCT` is 45, with the 1% rate
|
||||
tolerance as the guard; a chosen point on the floor carries `floor=1 note=floor, not optimum` in the chosen line and
|
||||
the record. Ember 2's climb starts from the chosen point.
|
||||
|
||||
RTX 4070 (same driver and class), 10 rows, every one ok, 55 C at most:
|
||||
|
||||
| Step | Clock cap | Power | Draw | Rate | MH/W |
|
||||
|---|---|---|---|---|---|
|
||||
| 0 (before) | unlocked | 100% 200 W | 106.0 W | 28.72 | 0.271 |
|
||||
| 1 to 4 | unlocked | 90, 80, 70, 60% | 106.0 W | 28.71 | 0.271 (the cap never binds) |
|
||||
| 5 | unlocked | 50% 100 W | 99.1 W | 28.70 | 0.290 |
|
||||
| 6 | 2794 MHz | 50% | 99.1 W | 28.70 | 0.290 |
|
||||
| 7 | 2484 MHz | 50% | 81.1 W | 28.73 | 0.354 |
|
||||
| 8 | 2173 MHz | 50% | 77.7 W | 28.74 | 0.370 |
|
||||
| 9 (chosen) | 1863 MHz | 50% | 75.6 W | 28.78 | 0.381 |
|
||||
|
||||
Per user: 30 W saved for no rate lost (+0.2%), 41% more hashes per watt; the floor again.
|
||||
|
||||
RX 9070 XT: no row. "TUNE aborted: the setting (0 MHz, 100% = 100 W) did not take within 30 s (card reports 0 W,
|
||||
acknowledged true)", twice. The cause was `Run::applied`: a power step demanded the limit read back in watts, and
|
||||
ADLX reports an offset, never watts, so no AMD power step could ever confirm. Fixed (bd7fcf4): a card with no limit
|
||||
readback is applied on the acknowledgement. The watts themselves were read on every tick (`amd_watts_source=
|
||||
engine_telemetry`, 363 nonzero engine samples, 456 nonzero app-state samples). The ladder reruns with kit-7. The
|
||||
"100 W" in that line is the percent scale printed as watts (owed: a unit word for AMD).
|
||||
|
||||
After the run (nvidia-smi, the engine gone, the installed miners not yet back): 5090 1845 MHz locked at 575 W;
|
||||
4070 at the 100 W limit. The installed app mined again within a minute: 5090 122.9 MH/s, 4070 28.8, 9070 XT 18.9,
|
||||
170.6 MH/s, 0 faults (console, 16:44Z). The 9070 XT's ADLX state read "gmax 0 plimit 0 factory 0" (before: factory
|
||||
1): zero offsets, the factory flag cleared by the engine's set of 0; the 3.0 coordinator's reset job puts it back.
|
||||
|
||||
### The re-point and the no-prompt proof (6 October 2026, 16:48 to 16:53Z)
|
||||
|
||||
`ember-repoint-pc1-1` (unelevated): task action before = the scratch copy; kit-7 (bd7fcf4) put in its place;
|
||||
`Start-ScheduledTask`; `1 reregister` written; helper.log at 16:48:42Z: "1 reregister ok: the task now runs
|
||||
C:\Users\Admin\AppData\Local\Programs\Igneum Miner\igneum-app.exe --power-helper"; action read back = the
|
||||
installed exe. Three seconds later the installed app's own caps went through the task with no prompt (helper.log:
|
||||
"nvidia-smi -i 0 -pl 460: Power limit for GPU 01:00.0 was set to 460.00 W from 575.00 W"; "-i 1 -pl 160: set to
|
||||
160.00 W from 100.00 W"). `ember-proof-pc1-2` (reads only): task state Running, run level Highest, user Admin;
|
||||
5090 221 W at 1845 MHz, 4070 75.8 W at 1860 MHz, 9070 XT 202 W; all three mining through the installed app. The
|
||||
Security log's process-start audit is off on PC 1, so a consent.exe count is unreadable; the helper's log is the
|
||||
record. Known in 0.3.13 on PC 1 until 0.3.14: the rows show the app's own earlier baseline (no /api/tune-progress
|
||||
to receive the run's result), and the 80% caps are the app's setting; the draw is the tuned one regardless because
|
||||
the driver holds the clock locks and the caps do not bind.
|
||||
|
||||
## 8a. Next-cut notes (for the 0.3.12 shipper)
|
||||
|
||||
For the 0.3.14 cut (6 October 2026, after run 6), on ember-tune:
|
||||
|
||||
| Commit | What | Where |
|
||||
|---|---|---|
|
||||
| (this cut) | every task command goes to the helper's fixed folder (`powertask::helper_dir` = `<platform data root>/app/sweep`, IGNEUM_APP_DATA ignored): a scratch-root measurement engine talking to the task wrote under its own root, where the task never reads (found 6 October 2026 planning the unelevated climb; the elevated runs set limits directly and never crossed it) | platform.rs `fixed_data_root`, powertask.rs, engine.rs `helper_cmd_dir`, main.rs + test |
|
||||
| (this cut) | a baseline result's row reads "Measured: ..." not "Tuned: ..." (`ember::result_line`); PC 1's 0.3.13 rows read "Tuned: 114.2 MH/s at 305 W" for a baseline | ember.rs, engine.rs + test |
|
||||
| bd7fcf4 | AMD: a power step with no limit readback is applied on the acknowledgement (run 6 aborted the 9070 XT's ladder at step 1 on "card reports 0 W") | ember.rs `Run::applied` + test |
|
||||
| 200362a | `task_exe` prefers the installed exe for every path that is not itself an install candidate; the helper's `reregister` verb; the clock ladder and floor to 45%; a chosen point on the floor says so; the fleet page's team rows | powertask.rs, ember.rs, engine.rs, site |
|
||||
| 5165de7, 9fba504, 5b0968f | Ember 2's Settings (goal, price, climb), £/day and the curve on the card; the playbook's AMD watts source and kit-7 preference; run 6 notes | ui, relay/playbooks, docs |
|
||||
|
||||
Earlier (shipped in 0.3.12 and 0.3.13 unless marked):
|
||||
|
||||
| Commit | What | Where |
|
||||
|---|---|---|
|
||||
| b671c8b | every `quit:` names its source; Power control alone decides; no cap at start under `--sweep` | main.rs, server.rs, engine.rs (separable) |
|
||||
| e600e63 | a second engine never runs the updater (`IGNEUM_APP_NO_OTA`, implied by `--sweep`) | engine.rs (6 lines, separable) |
|
||||
| 1e9550e | the elevated job path's output file is followed while the script runs, so the 5-minute progress reports carry its lines (a 35-minute run that never mined showed only "script running" on 6 October 2026); the tune playbook's watchdog fails a run that mines nothing within 120 s of its first status line, with the engine's last log line in the RESULT | jobrun.rs `follow_file`, relay/playbooks/ember-tune-pc1.ps1 |
|
||||
|
||||
## 9. Open
|
||||
|
||||
- The NVIDIA clock readback: `nvidia-smi -lgc` is confirmed only through the core clock during the hold (a mean over
|
||||
the cap by 5% marks the step `unapplied`); the first run with Power control on tells whether the driver honours
|
||||
the lock on the 5090 under this kernel.
|
||||
- ADLX on RDNA 4 exposes no memory-clock setter; the memory-clock mark is the guard. The telemetry agent's 9070 XT
|
||||
sweep tells whether a core cap drags the memory clock on that card.
|
||||
- The confirm plan's neighbour is one step; a second neighbour (the other knob) would cost 75 s more and catch a
|
||||
prior that is wrong on both knobs.
|
||||
- Intel: no knob yet; the row says measure only.
|
||||
|
||||
## 7a. One administrator approval, ever (0.3.13; the project lead, 6 October 2026, 11:50 UTC)
|
||||
|
||||
What 0.3.12 does: Power control on raises one prompt and sets every cap in that step; every later cap (an app start, a
|
||||
|
|
@ -184,21 +350,18 @@ process, registry key or other binary is reachable through it. Tests: `powertask
|
|||
non-digit or extra argument, the arguments reach nvidia-smi as a list, the registration is per-user, highest,
|
||||
trigger-less and quote-safe, a stale command file runs nothing).
|
||||
|
||||
## 8a. Next-cut notes (for the 0.3.12 shipper)
|
||||
## Fleet priors from rented cards (6 October 2026, branch gpu-fleet)
|
||||
|
||||
| Commit | What | Where |
|
||||
|---|---|---|
|
||||
| b671c8b | every `quit:` names its source; Power control alone decides; no cap at start under `--sweep` | main.rs, server.rs, engine.rs (separable) |
|
||||
| e600e63 | a second engine never runs the updater (`IGNEUM_APP_NO_OTA`, implied by `--sweep`) | engine.rs (6 lines, separable) |
|
||||
| 1e9550e | the elevated job path's output file is followed while the script runs, so the 5-minute progress reports carry its lines (a 35-minute run that never mined showed only "script running" on 6 October 2026); the tune playbook's watchdog fails a run that mines nothing within 120 s of its first status line, with the engine's last log line in the RESULT | jobrun.rs `follow_file`, relay/playbooks/ember-tune-pc1.ps1 |
|
||||
Measured by `tools/fleet/box-ember.sh` on Vast.ai containers, the 0.3.12 CUDA worker against the box's devnet node, 15 s settle and 60 s hold per step. `nvidia-smi -pl` and `-lgc` are refused inside the containers (the host's driver holds the knobs), so every ladder is its baseline step only: the untuned point per model, as `plan: baseline` records in `relay/lib/ember.mjs` terms (they summarise beside a prior, never set one). A tuned prior per model needs bare metal or a VM with the driver inside.
|
||||
|
||||
## 9. Open
|
||||
| Card | Driver | MH/s | W | MH/W | Power default W | Clock max MHz | Plan |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| RTX 3060 | 580.126.20 | 24.59 | 106.1 | 0.2318 | 170.0 | 2100.0 | baseline |
|
||||
| RTX 4060 Ti | | 17.64 | 73.5 | 0.24 | | | baseline |
|
||||
| RTX 4070 | 580.159.04 | 24.77 | 91.3 | 0.2713 | 200.0 | 3120.0 | baseline |
|
||||
| RTX 4090 | 595.71.05 | 52.24 | 179.9 | 0.2904 | 450.0 | 3105.0 | baseline |
|
||||
| RTX A5000 | 580.82.09 | 47.6 | 222.3 | 0.2141 | 230.0 | 2100.0 | baseline |
|
||||
| RTX 5090 | 580.173.02 | 98.12 | 258.2 | 0.38 | 575.0 | 3090.0 | baseline |
|
||||
| RTX 5070 | 595.84 | 56.83 | 136.4 | 0.4166 | 250.0 | 3135.0 | baseline |
|
||||
|
||||
- The NVIDIA clock readback: `nvidia-smi -lgc` is confirmed only through the core clock during the hold (a mean over
|
||||
the cap by 5% marks the step `unapplied`); the first run with Power control on tells whether the driver honours
|
||||
the lock on the 5090 under this kernel.
|
||||
- ADLX on RDNA 4 exposes no memory-clock setter; the memory-clock mark is the guard. The telemetry agent's 9070 XT
|
||||
sweep tells whether a core cap drags the memory clock on that card.
|
||||
- The confirm plan's neighbour is one step; a second neighbour (the other knob) would cost 75 s more and catch a
|
||||
prior that is wrong on both knobs.
|
||||
- Intel: no knob yet; the row says measure only.
|
||||
The TUNE records themselves (`ember.json` per instance under `~/Desktop/fleet/<instance>/`) carry the step, the mean core and memory clock and the hottest reading; `relay/lib/ember.mjs parseRecords` reads the `TUNE {...}` line each ladder prints.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ The unfunded half is the half that comes after the chain exists and before and j
|
|||
|
||||
## 4. What the 1% fee could be, and why it is not counted
|
||||
|
||||
The fee is 1% of rewards on the official client. Rewards in year one are 963 million IGN (ramp included, `docs/analysis/security-budget.md`). At the low, base and high price inputs of that analysis the fee's ceiling, with every miner on the official client, is USD 48,000, 193,000 and 963,000 a year. Those are inputs, not expectations, and the share of miners on the official client is unknown. Nothing in section 2 is funded against them.
|
||||
The fee is 1% of the producer share on the official client, default on and switchable: the fee template moves only the producer payout (80% of emission), and the proving pool is paid per record and carries none of it. Rewards in year one are 963 million IGN (ramp included, `docs/analysis/security-budget.md`), so the producer share is 770 million. At USD 0.005, 0.02 and 0.10 per IGN the fee's ceiling, with every miner on the official client, is USD 38,520, 154,080 and 770,400 a year (corrected 6 October 2026 from 48,000, 193,000 and 963,000, which took 1% of all rewards and overstated the ceiling by a quarter; the Horizon economy lane, `docs/analysis/horizon/economy-and-utility.md` section 4.4). Those are inputs, not expectations, and the share of miners on the official client is unknown. Nothing in section 2 is funded against them.
|
||||
|
||||
## 5. Rules
|
||||
|
||||
|
|
|
|||
65
docs/plans/gpu-fleet.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# The rented GPU fleet: the standing rule and the one-shot rule
|
||||
|
||||
the project lead's ruling, 6 October 2026, 19:50 UK ("keep rented cards up"), written as the fleet agent runs it. Tooling in
|
||||
`tools/fleet/` on branch `gpu-fleet`; every box operation goes through `tools/fleet/lib/` (the box library) and the
|
||||
box-side scripts it ships.
|
||||
|
||||
## Two kinds of box
|
||||
|
||||
| Kind | Rule | Examples |
|
||||
|---|---|---|
|
||||
| Standing | stays up, never destroyed on a job's end; replaced in the same shape when its host dies; node under a supervisor; version follows the live manifest; never joins an experiment | the 13 live-devnet boxes of 6 October (USD 3.50/h), the Devnet 2 set (seed, 3 miners, 2 provers) |
|
||||
| One-shot | rented for one measurement, destroyed the minute its measurement is in (a running meter is a bug) | the memory-matrix cards, the 8x rigs, the wave pods, the RISC Zero box |
|
||||
|
||||
The standing set's size and cost: 16 on the live devnet (a mix: 5090, 4090s, 3090s, 4070, 3080, A5000, an L4, at least
|
||||
two AMD when Vast reopens) plus 6 on Devnet 2, about USD 8 an hour, USD 200 a day, inside the daily budget (USD 250 to
|
||||
1,000). Tonight's 13 cost USD 3.50 an hour (USD 84 a day); the L4, the AMD pair and the Devnet 2 six are owed to the
|
||||
roster as providers free cards (RunPod gave no pod of 8 card types from 18:59Z, Vast refuses every new rent).
|
||||
|
||||
## What a standing box carries
|
||||
|
||||
| Piece | Where | What it does |
|
||||
|---|---|---|
|
||||
| `box-standing.sh` (the supervisor) | `/root/fleet/in/`, started once by `lib/standing.py install` | every 60 s restarts the node, the miner loop (CUDA worker, pack re-exported on seed change) and the prover when gone; every 10 min writes a `RESULT standing` line (uptime, node pid and binary, blocks, DAA, peers, synced, exec tip, miner rate, prover) to `/root/fleet/out/standing.log`; runs the shipper's recovery recipe (`box-exec-snapshot.sh`) when consensus is synced above 1,000 blocks and the exec tip reads 0 |
|
||||
| `/root/fleet/standing.node` | the box | the node binary the supervisor runs; the supervisor adopts whatever node already runs when the file is absent (a canary's newer binary is never downgraded) and restarts the node only when a publish rewrites the pointer |
|
||||
| the registry row | `~/Desktop/fleet/boxes.json` | `standing: true`, `role: live|dn2`, `standing_since`, `node_sha16_wanted` after a publish |
|
||||
| `lib/standing.py` | the Mac | `roster` (role, card, price, uptime), `install`, `check` (ssh alive, supervisor up, synced, exec moving, prover, node binary against the wanted sha), `update` (the manifest's hive package onto the box, pointer rewritten), `rerent` (same card and provider, label suffixed `-r<n>`, old row marked destroyed), `loop` (check every 10 min, re-rent after two dead checks, report what is behind) |
|
||||
| the fleet page | `dl.igneum.network/fleet-22adafa34bc2/` | a `standing` block (count, USD/h, USD/day, roles, the rule) and `standing`, `role`, `uptime_h` on every box row |
|
||||
|
||||
A publish on the standing set is a script, not the loop: `tools/fleet/publish-0315.py` is the first (the Linux igneumd,
|
||||
igneum-miner and the generator-4 workers over the package's, the pointer rewritten, the read-back table of version line,
|
||||
digest and worker sha per box). The loop reports a box behind its wanted sha; it does not move binaries by itself.
|
||||
|
||||
## Ports
|
||||
|
||||
A standing box should carry a mapped p2p port whenever the provider allows it (RunPod: request `26611/tcp` at rent;
|
||||
Vast: none of tonight's offers mapped one), so that the live devnet stops being a star in which every rented node dials
|
||||
only the two hand nodes and the hub: the 18:39Z to 19:4xZ finality pause coincided with the hands being down, and run A
|
||||
(10 blocks/s on a star, 77 to 84 percent red blocks) showed the same weakness on Devnet 2. Tonight's inbound-capable
|
||||
nodes were igneum-build-1 (26611) and dn2-seed's one mapped port; a true mesh needs pods rented with a p2p port.
|
||||
|
||||
## Clock
|
||||
|
||||
- 19:41Z: the 13 live boxes converted (supervisor started, every node synced, every prover up).
|
||||
- 20:00Z: the 10 percent rule, after the finality pause the fleet caused (`lib/standing.py weight_check`).
|
||||
- 20:42Z to 20:48Z: the disk class (the prover's exports) found on the hub's third death; the supervisor prunes, `disk-sweep.py` watches, box-prover.py caps its own directory.
|
||||
- 20:58Z: Devnet 2 standing at 1 block/s: the seed on igneum-build-1 (188.40.146.49:26611), miners dn2-seed, dn2-1, dn2-2, dn2-3, provers on dn2-1 and dn2-2 (the floor host copied from p1-4090); the gate owed until the rehearsal chain ends and dn2-seed's mapped port is free.
|
||||
- 21:44Z to 21:53Z: publish 1 of 0.3.15 on the 14 live standing boxes (`publish-0315.py`: node 7f0bde70 = f1ea7a38, miner, generator-4 workers; the thirteen-field object, digest b18ed271).
|
||||
- 22:18Z to 22:24Z: the forced poisoned-peer confirmation on the live network (relay on 6615571c over a 1026 datadir, a 713ef876 miner into it, the f1ea7a38 target refusing six relayed 1026 blocks with the old rule's line and never itself rejected).
|
||||
- 22:49Z: publish 2 (the sixteen-field object: exec_restart_state_root, class v4 floor 831,600, window 86,400) on the 14 live standing boxes by `~/Desktop/fleet/move-16.py` (file to /root/fleet/override.json, the previous kept as override-13.json, node restarted by the supervisor, miner with it); expected digest eada4bda; `~/Desktop/fleet/expected-digest` rewritten so the five-minute check posts on any box left on b18ed271. The Devnet 2 five stay on their own object (4a0b8726).
|
||||
- Owed: the L4 and the AMD pair when a provider reopens; the ports on re-rent.
|
||||
|
||||
## The 10 percent rule (6 October 2026, 20:00Z, from the finality pause)
|
||||
|
||||
What happened: the class v4 rehearsal job took the GPUs of 13 live-devnet miners between 18:27Z and 18:30Z (their
|
||||
live nodes stayed up and synced, but a voter's weight is its blue blocks, and a miner that stops mining stops being
|
||||
a voter), and with seven earlier leavers that was 42.7 percent of the frozen voter table; finality rule v3 then holds
|
||||
the pause for one full window, and the first lock after 18:39:36Z is expected at about 20:40Z. The fleet's read that
|
||||
"the boxes never left the devnet" was true of the nodes and wrong about the weight.
|
||||
|
||||
The rule: never remove more than 10 percent of the live devnet's 30-day weight in any hour. Weight is counted by blue
|
||||
blocks per key over the window, read from the hub; any experiment that borrows live miners does it in slices with an
|
||||
hour between slices; a standing box's miner is never stopped (or its GPU shared with a second worker) by a job
|
||||
without that check. `lib/standing.py weight_check <labels>` is the gate: it reads the hub's blue blocks per payout key
|
||||
over the window, sums the share of the labels asked for, and refuses the job when the share since the last hour's
|
||||
removals exceeds 10 percent; every fleet job that touches a standing box's miner calls it first.
|
||||
124
docs/plans/hands-on-build-1.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# The devnet hands move to igneum-build-1 (node 1 and the observer)
|
||||
|
||||
the project lead's decision, 6 October 2026: the Mac runs nothing the network depends on. After the 0.3.15 cut tonight, node 1 and the
|
||||
observer leave the Mac's launchd agents and run on igneum-build-1 (188.40.146.49, Falkenstein, the build server of
|
||||
docs/plans/build-server.md) as systemd units. The shipper (owner of infra/devnet/restart-hand-nodes.sh and the exec recovery
|
||||
recipe) and the build-server agent run it together on the shipper's "0.3.15 live" line.
|
||||
|
||||
## 1. What runs on the Mac today (read 6 Oct 2026, 19:5x UK)
|
||||
|
||||
| Hand | How it runs | Flags that matter | Data |
|
||||
|---|---|---|---|
|
||||
| node 1 | launchd `network.igneum.devnet.node1`, KeepAlive, under `caffeinate -dims`, binary `igneum-wt-ship0314/vendor/igneum-node-0314/target-integration/release/igneumd` (0.3.14), log `~/Library/Logs/Igneum/node1.out` | `--devnet --enable-unsynced-mining --appdir=/tmp/igneum-devnet/node1 --rpclisten=0.0.0.0:26610 --listen=0.0.0.0:26611 --evm-rpclisten=127.0.0.1:26791 --addpeer=188.245.5.161:26611 --addpeer=192.168.68.67:26611 --override-params-file=/tmp/igneum-devnet/override-v3.json --igneum-exec-snapshot=/tmp/igneum-devnet/node1-copy-snapshot.bin,ac101f13... --nodnsseed --disable-upnp --nologfiles --yes` | 856 MB (`consensus`, `evm` with exec-snapshot.bin 127,564,588 B and .prev, `meta`) |
|
||||
| observer node | launchd `network.igneum.devnet.observer`, KeepAlive, same binary, log `observer.out` | `--appdir=/tmp/igneum-devnet/observer-v4 --rpclisten=127.0.0.1:26640 --rpclisten-json=127.0.0.1:28640 --listen=127.0.0.1:26641 --addpeer=127.0.0.1:26611 --addpeer=188.245.5.161:26611 --addpeer=192.168.68.67:26611` + the same override and snapshot flags; no EVM listener | 822 MB |
|
||||
| observer process | `tools/observer/run.sh` loop (nohup, since 5 Oct 20:39) running `node tools/observer/observer.mjs` from the shared checkout, `IGNEUM_RPC=ws://127.0.0.1:28640`, `IGNEUM_EVM_RPC` default `http://127.0.0.1:26800` (the Miner app's node), `DATABASE_URL` from `~/.config/igneum/env`; `tools/observer/autosync.sh` (since 4 Oct) fast-forwards the shared checkout and restarts it on observer changes | writes Neon (`live_*` tables); `/api/live` and `/live` read Neon only |
|
||||
|
||||
Also found: the Mac's own miner (`igneum-miner`, pid 7721) holds a gRPC connection to node 1 on 26610. The override file today:
|
||||
`{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000,"finality_v3_activation_daa":135200,"program_class_v3_activation_daa":154800,"proving_v1_activation_daa":154800,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":198000,"exec_restart_number":27276,"exec_restart_hash":"bb45cf0d...","exec_restart_trust_daa":200000}`.
|
||||
Node 1's last exec lines: `exec state loaded from a snapshot: tip 141700 ...` and `exec sync: resumed from .../node1/igneum-devnet/datadir/evm/exec-snapshot.bin (tip 141700, 127564588 bytes, sha256 0x67637c55...)`.
|
||||
|
||||
## 2. What runs on the box afterwards
|
||||
|
||||
| Unit | Runs | Ports | Files |
|
||||
|---|---|---|---|
|
||||
| `igneum-node1.service` | `/srv/hands/bin/run-node1.sh` -> `/srv/hands/bin/igneumd` (0.3.15 Linux, the box's own build), `--appdir=/srv/hands/node1`, `--externalip=188.40.146.49` | p2p `0.0.0.0:26611` (ufw open); gRPC `127.0.0.1:26610`, wRPC JSON `127.0.0.1:28610`, EVM `127.0.0.1:26791` on loopback | `/srv/hands/hands.env` (IGNEUMD, OVERRIDE, SNAPSHOT, EXTERNAL_IP, PEERS), `/srv/hands/override.json`, `/srv/hands/node1-copy-snapshot.bin` |
|
||||
| `igneum-observer-node.service` | `run-observer-node.sh`, `--appdir=/srv/hands/observer-node`, peers node 1 on the box and the seed | p2p `127.0.0.1:26641`, gRPC `127.0.0.1:26640`, wRPC JSON `127.0.0.1:28640`, EVM `127.0.0.1:26840` (new: the observer's proving feed came from the Miner app's node on the Mac) | same env and override |
|
||||
| `igneum-observer.service` | `/usr/local/bin/node tools/observer/observer.mjs` in `/srv/observer/igneum` (a clone of the mirror `/srv/igneum.git`, master), `EnvironmentFile=/srv/observer/env` (mode 600, owner build: DATABASE_URL copied from the Mac's `~/.config/igneum/env`, `IGNEUM_RPC=ws://127.0.0.1:28640`, `IGNEUM_EVM_RPC=http://127.0.0.1:26840`; never in the repo), Restart=always 3 s (run.sh's loop) | outbound to Neon only | `/srv/observer/env` |
|
||||
| `igneum-observer-sync.timer` | every 5 min `observer-sync.sh`: fast-forward the clone from the mirror, restart the observer when `tools/observer` or `site/lib` changed (autosync.sh's job; the mirror is fed by every build-remote.sh and run-from-mac.sh push) | | |
|
||||
|
||||
All units: `Restart=always`, journald (`journalctl -u igneum-node1 -f`), `MemoryMax=24G` on the nodes, enabled for reboot, run as user
|
||||
`build` (one uid on a single-purpose box; the units are the separation). Installer: `infra/build-server/hands/install-hands.sh`
|
||||
(idempotent, inert: enables, never starts). Mover: `infra/build-server/hands/move-hand.sh` (dry run by default, `--go` executes).
|
||||
|
||||
## 3. Ports, names, firewall
|
||||
|
||||
| Port | On the Mac today | On the box | ufw |
|
||||
|---|---|---|---|
|
||||
| 26611 TCP | node 1 p2p, open | node 1 p2p, open, `--externalip` so peers learn it | open already |
|
||||
| 26610 TCP | node 1 gRPC on `0.0.0.0` (the Mac's miner connects to it) | gRPC on loopback; a Mac tool reaches it through `ssh -L 26610:127.0.0.1:26610 build@188.40.146.49` | not opened (decision for main: open it to a fixed IP list if a miner must feed node 1 from outside) |
|
||||
| 26791 TCP | node 1 EVM RPC, loopback | loopback | closed |
|
||||
| 26640, 28640, 26641 TCP | observer node, loopback | loopback (+ EVM 26840) | closed |
|
||||
| 26811 TCP | (the TESTNET seed p2p port, not an RPC port: infra/seed-nodes/config.sh) | unused by the hands | open from provision; harmless |
|
||||
|
||||
DNS (deSEC, main adds): `node1.devnet.igneum.network A 188.40.146.49`, `observer.devnet.igneum.network A 188.40.146.49`. The
|
||||
observer node listens on loopback only, so its name is for the future public endpoint and for the runbooks' wording. The public
|
||||
API (`/api/live`, `/live`) reads Neon and is unchanged.
|
||||
|
||||
## 4. The move, one hand at a time (run on the shipper's "0.3.15 live" line)
|
||||
|
||||
| Step | Command (Mac) | What happens | Proof |
|
||||
|---|---|---|---|
|
||||
| 0 | `ssh root@188.40.146.49 'bash -s' < infra/build-server/hands/install-hands.sh` | units, run scripts, dirs, the observer clone; nothing started (done 6 Oct, see section 6) | `systemd-analyze verify` clean, units enabled and inactive |
|
||||
| 1 | `move-hand.sh binary --node /Users/joshm/Projects/igneum-wt-ship0315/vendor/igneum-node-0315` | the 0.3.15 Linux igneumd built ON the box (tools/build-remote.sh), installed as `/srv/hands/bin/igneumd-0.3.15-<sha>`, commit string checked; the Mac's override file and the exec snapshot copied; hands.env pointed at them | `igneumd --version`, commit in strings, sha256 lines |
|
||||
| 2 | `move-hand.sh observer-node --go` | hot rsync of `/tmp/igneum-devnet/observer-v4` (822 MB) while the Mac node runs; `launchctl bootout` of the Mac's observer agent (KeepAlive would restart a killed pid); final rsync (the delta, seconds, node stopped so RocksDB is consistent); `systemctl start igneum-observer-node` | the unit's first `[igneum-exec] exec sync: resumed from ...` line and its first `Accepted N blocks` lines, printed by the script |
|
||||
| 3 | `move-hand.sh observer --go` | `/srv/observer/env` written (scp, mode 600, owner build); the Mac's autosync.sh, run.sh and observer.mjs stopped FIRST (two writers would duplicate Neon rows), then `systemctl start igneum-observer` | the observer's first journal lines (`rpc load`, events); `/api/live` fresh within a minute |
|
||||
| 4 | `move-hand.sh node1 --go` | the same as step 2 for node 1 (856 MB); node 1 is the last node the Mac serves, the observer node on the box already peers with the seed, so the network never loses both hands | node 1's first exec line and `PoW accepted` lines on the box |
|
||||
| 5 | `move-hand.sh unload --go` | the two plists moved aside so a login never brings the Mac hands back; the Mac's miner loses node 1's RPC (section 5) | `pgrep igneumd` on the Mac shows only the wallet's node |
|
||||
| 6 | `move-hand.sh status`, 10 minutes later | both nodes at the network tip, observer writing, `/live` current | the status output in this plan's section 6 |
|
||||
|
||||
Exec recovery on the box: each node resumes from its data dir's `evm/exec-snapshot.bin` (copied with the data dir); if that file
|
||||
is bad or missing, `--igneum-exec-snapshot=/srv/hands/node1-copy-snapshot.bin,<sha256>` (the Mac's recovery snapshot, copied in
|
||||
step 1) is taken, as the launchd agents do today; the override's `exec_restart_number`/`exec_restart_hash`/`exec_restart_trust_daa`
|
||||
travel unchanged. A snapshot from the seed is the fallback the shipper's recipe names; the p2p snapshot path refuses one below the
|
||||
node's tip (CLAUDE.md, Devnet 2 rules).
|
||||
|
||||
Rollback at any step: the Mac's plist is still in `~/Library/LaunchAgents` until step 5; `launchctl bootstrap gui/$(id -u) <plist>`
|
||||
brings a hand back on the Mac within 10 s, and the box unit is stopped with `systemctl stop`. Data dirs are copies; nothing is deleted
|
||||
on the Mac.
|
||||
|
||||
## 5. Decisions and consequences for main (DECIDED by main, 6 October 2026, 19:1x UTC)
|
||||
|
||||
| Question | Decision |
|
||||
|---|---|
|
||||
| The Mac's miner on node 1's gRPC | stops (paused by the project lead's order anyway; the Mac mines nothing) |
|
||||
| The LAN peer 192.168.68.67 (a PC) | the shipper adds `--addpeer=188.40.146.49:26611` to the PCs' and the Mac's app node args in the 0.3.15 update; the seeds carry the public peers already |
|
||||
| node 1's gRPC 26610 | loopback only on the box; Mac tools tunnel |
|
||||
| DNS | `node1.devnet.igneum.network` and `observer.devnet.igneum.network` -> 188.40.146.49 exist in deSEC (checked: ns1.desec.io answers both) |
|
||||
|
||||
The table below is the reasoning that led to them.
|
||||
|
||||
|
||||
| Finding | Means | Proposed |
|
||||
|---|---|---|
|
||||
| The Mac's own miner (`igneum-miner` pid 7721) mines through node 1's gRPC 26610 | after step 4 it loses its node; the project lead's rule says the Mac runs nothing the network depends on, and a miner is hashrate, not a dependency | either it stops with node 1, or it follows through an ssh tunnel to the box (`ssh -L 26610:...`); main decides, default: it stops |
|
||||
| `192.168.68.67:26611` is a LAN peer of both hands | unreachable from the box; the box peers with the seed 188.245.5.161 and the two hands peer with each other | hands.env `PEERS=188.245.5.161:26611`; whoever runs 192.168.68.67 adds `--addpeer=188.40.146.49:26611` if it relied on node 1 |
|
||||
| CLAUDE.md "Running agents on this Mac" says the box never hosts a live-devnet node (my R6, 6 Oct 18:xx) | contradicted by the project lead's decision the same evening | rewritten in this commit: the box hosts the two hands as units; it still holds no secret beyond `/srv/observer/env` (DATABASE_URL, mode 600) |
|
||||
| The observer's proving feed on the Mac read the Miner app's node (26800) | on the box there is no app node | the observer node gets `--evm-rpclisten=127.0.0.1:26840` (0.3.15 runs the proving build) and the observer reads it; if its shard plans lag node 1's, point `IGNEUM_EVM_RPC` at node 1's 26791 |
|
||||
| autosync.sh followed origin/master from GitHub; the box has no GitHub credential | the box's clone follows the MIRROR, which moves only when a Mac agent pushes (every build-remote.sh and run-from-mac.sh run pushes the branch it builds; run-from-mac.sh pushes every branch) | enough today; a read-only deploy key on the box (generated there, added by main to the repository) would make it follow GitHub directly, open |
|
||||
| Two hands stop for one to three minutes each during the move (the final rsync and the start) | the other hand serves throughout; the observer feed pauses once for about a minute (step 3) | accepted by the order above |
|
||||
| Data dirs are rsynced hot then with the node stopped | the hot pass moves 99 percent of 1.7 GB with the hands up; the stopped pass is the delta | the Mac's upload rate decides the hot pass (minutes); measured in section 6 |
|
||||
|
||||
## 6. Run log (filled as it happens)
|
||||
|
||||
Run on 6 Oct 2026 (UTC; box clock is UTC+2). Inputs: the shipper's "0.3.15 live" became 0.3.16 (same tree, f1ea7a38); publish 2 live with the sixteen-field object, digest eada4bda8aa8368c2b2c3d17744bc7a70a0ff0e996dad681884d3ac5de1207eb; main's gate: port 26611 on the box was held by the fleet agent's Devnet 2 seed, moved to 26621 at 23:06:57 ("26611 free on the box").
|
||||
|
||||
| Time | Step | Result |
|
||||
|---|---|---|
|
||||
| 23:06 to 23:14 | `binary` (tree from the Mac's node1 agent: igneum-wt-ship0315/vendor/igneum-node-0315, f1ea7a38) | /srv/hands/bin/igneumd-2.1.0-f1ea7a38, sha256 7f0bde70cf2e72a3a9479ba6421f2b37162c3dd4391bf413717c99e6f168668d, commit string f1ea7a3 present; the sixteen-field override from the live manifest's consensus.override; recovery snapshot ac101f13... as the fallback flag. First attempt died silently after the version print: `igneumd --version` exits 1 (fixed) |
|
||||
| 23:06 to 23:10 | hot pre-sync | observer-v4 920 MB in 97 s, node1 937 MB in 98 s, hands running |
|
||||
| 23:15:11 to 23:15:32 | `observer-node --go` | first executing line 01:15:28 box time: "[igneum-exec] exec state loaded from a snapshot: tip 149781 e98b33f2..., state root 0x55a5892f..."; digest eada4bda... MATCH; "Program class v4 from the override file: active from epoch 231"; listeners 26640, 28640, 26641, 26840 loopback; the hand was down about 10 s |
|
||||
| 23:15:52 to 23:16:00 | `observer --go` | /srv/observer/env written (600, build); the Mac's autosync, run.sh, observer.mjs stopped first; started 23:15:56, "subscribed on igneum-devnet, node 2.1.0", 2871 checkpoint states seeded. Two minutes of getBlockDagInfo timeouts while its node settled (/api/live stale 41 s at 23:16:12), none after; rpc load 150 wRPC/min, 1366 EVM/min |
|
||||
| 23:18:36 to 23:18:58 | `node1 --go` | first executing line 01:18:53: "exec state loaded from a snapshot: tip 149806 f54ef53d..., state root 0xe1cd365e..." and "exec sync: resumed from /srv/hands/node1/.../exec-snapshot.bin (tip 149806, 142713733 bytes)"; digest MATCH; p2p 0.0.0.0:26611, RPC loopback; IBD of 419 headers from 213.173.107.74 01:19:12 to 01:21:42, then PoW blocks accepted from DAA 227716 (420 in the next 2 min, 6 established peers) |
|
||||
| 23:22:15 | `unload --go` | both plists moved aside (.moved-to-build-1-20261006); no hand igneumd on the Mac |
|
||||
| 23:22 | steady state | igneum-node1, igneum-observer-node, igneum-observer active; /api/live age 1.4 s, height 149878; box load 27 to 34 (capacity layer and CI runner share it) |
|
||||
|
||||
### 6a. The 0.3.17 hotfix on the box and the seed (7 Oct 2026, 05:30 to 05:32 UTC)
|
||||
|
||||
The cut moved twice in the night (12153428 failed its canary; b3c228fa became 5899f603 with the test-only fixes and the toolchain pin), so the
|
||||
pairs were built three times; the shipped one is fork 5899f603 paired with igneum release-0.3.17 at 250fd371 (the tool's first line reads
|
||||
"pairs with igneum 250fd371 (detached): igneum-pow 0.2.0"). Read-back per node: first executing line, commit string 5899f603 in the running
|
||||
binary, "Consensus params digest" eada4bda... from the journal, and the engine from the binary (6 igneum-pow/src/ paths; this tree has no
|
||||
igneum_getNodeInfo, the RPC answers -32601).
|
||||
|
||||
| Time | Step | Result |
|
||||
|---|---|---|
|
||||
| 05:30:41 | `move-hand.sh binary --node <5899f603 tree> --override-json <live consensus.override>` | igneumd-2.1.0-5899f603, sha256 401bfd54d3cb58736c18ada6c1796b62839a5bc02addcc6bb4ca01d0879810f8 (49,720,416 B); the 16-field object (byte-equal to the 23:14 one; the manifest reads 0.3.17); recovery snapshot as the fallback flag |
|
||||
| 05:30:46 to 05:30:57 | `move-hand.sh restart observer-node --digest eada4bda... --go` | first executing line 07:30:50 box time "exec state loaded from a snapshot: tip 157682 f2e6041c..."; PoW accepted at once; commit string present; digest MATCH; engine igneum-pow from the binary; the observer process reconnected by itself (1 tick failure in the first minute, none after); down about 6 s |
|
||||
| 05:31:12 to 05:31:22 | `move-hand.sh restart node1 ... --go` | first executing line 07:31:16 "tip 157689 f6cfb8bf..."; PoW accepted from daa 253285; commit string present; digest MATCH; engine igneum-pow; after: 11 peers on 26611, 126 blocks in the next minute, /api/live age 0.6 s |
|
||||
| 05:31:36 to 05:32:10 | `IGNEUMD_LINUX=<class-seed igneumd 39165c1f...> IGNEUMD_LINUX_SHA256=... infra/devnet/restart-seed.sh '<object>'` | the script's own check "binary needs GLIBC 2.34, the seed has 2.36"; unit active 05:32:00; /opt/igneum/v4/bin/igneumd sha256 39165c1f..., 2 commit strings 5899f60, 6 igneum-pow paths; digest eada4bda...; program class v4 from epoch 231; exec resumed from its own snapshot at tip 157696; 265 blocks in the two minutes after; down about 24 s. The seed's igneum-miner is not part of restart-seed.sh |
|
||||
|
||||
Held for 0.3.18: the 12153428 pairs (hands igneumd f0f2db86..., seed igneumd d8d431e5...). Not byte-for-byte with the shipper's own
|
||||
native build of the same inputs (d712b498): the two trees sit at different paths on the box and prost's generated code embeds OUT_DIR; a
|
||||
"reproduced" row stays a same-tree comparison unless both sides pass --remap-path-prefix.
|
||||
|
||||
Open after the move: the Mac's Igneum Miner app (0.3.16, running since 23:12) did NOT start its own igneumd once 26610/26611 were free (nothing bound them five minutes later; the only Mac igneumd is the Wallet's on 26620/26621/26800). A job never restarts the installed app, so the app lane or main decides how its node starts. The observer's proving feed now comes from the observer node's own EVM listener 26840.
|
||||
232
docs/plans/launch-pack.md
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
# The launch pack: gates and text, no protocol (mission item 10)
|
||||
|
||||
7 October 2026, 10:1x to 11:xx UK, the launch-pack lane (branch `launch-pack`, worktree `igneum-wt-launch-pack`, from master b92a5fd4). Mission item 10 of `docs/analysis/mission/mission.md` 2.10, built from `past.md` sections 3 and 4, `future.md` section 8, `reinvent.md` 3.1 and 4.1, `docs/plans/testnet-go.md` and the litepaper. This is an operations document: `docs/plans` is not on the public export list, so it may name the owner; the text handed to the site lane in section 4 may not, and `tools/ci/launch-gates-check.mjs` greps it.
|
||||
|
||||
the project lead's three decisions of this morning, written in everywhere below:
|
||||
|
||||
| Decision | the project lead's word (7 October 2026, 10:1x UK) | Where it lands |
|
||||
|---|---|---|
|
||||
| The proving customer | A signed proving customer IS a mainnet gate: one signed customer paying for proofs at a published rate, or a signed letter of intent with a volume, before mainnet, not before the testnet | `testnet-go.md` LG-11; the journey's phase 4 gate loses "one rollup signs for testnet" (section 4, item E); ledger X13 status (section 4, item F) |
|
||||
| The certificates | APPROVED: Windows EV Authenticode and an Apple Developer organisation account, both under Igneum Labs LTD, executed the day the entity exists | LG-3; the runbook in section 2; the signing step in 2.4 |
|
||||
| The disclosure prize | YES, USD 50,000; payer Igneum Labs LTD; staged until the entity address exists on its documents and the project lead's publish word (`docs/plans/cryptanalysis.md` 3.3, branch `cryptanalysis`, 43d9700b) | LG-13; nothing public until the word |
|
||||
|
||||
## 1. The gates
|
||||
|
||||
The thirteen gate rows are in `docs/plans/testnet-go.md`, section "Launch gates", each with its check, its state today and its owner. The check that keeps them honest: `node tools/ci/launch-gates-check.mjs` fails when a row has no check, when a check names a repository path that does not exist, when the handoff text in section 4 carries a served-page pattern or an em dash, or when one of the eight regulatory sentences is missing or out of order. Its `--self-test` fires on each of those. Both run in `tools/ci/pre-push.sh`.
|
||||
|
||||
What this lane built, and what each gate still needs:
|
||||
|
||||
| Gate | Built here | Still owed, by whom |
|
||||
|---|---|---|
|
||||
| LG-1 income per tier | `tools/launch/income-tiers.json` (the measured rows, each with its source), `tools/launch/income-tiers.mjs` (the generator, `--check`), `tools/launch/income-tiers.test.mjs`, `docs/analysis/income-tiers.md` (public) | re-generate at the 0.4.0 cut from class v4 rates (the shipper); the RTX 4060 and Apple wall power, an Intel row (the fleet lane) |
|
||||
| LG-2, LG-6, LG-7, LG-9, LG-10, LG-12 the hash-origin report | `tools/observer/hash-origin.mjs` (the job: `--dry`, `--write`, `--post`, `--fixture`), its fixture and test; ran read-only on the live devnet tables today | the Devnet 2 observer with a table prefix (fleet lane); the fleet registry's key file (fleet lane); the systemd timer (build-server lane); the two X5 columns (the observer owner); the pool statement format (the pool lane) |
|
||||
| LG-3 signed installers | the runbook (section 2) and the signing-step specification (2.4) | the project lead: the entity's documents, the two enrolments (section 5); the shipper: the step |
|
||||
| LG-4 first-share time | the measurement specification (2.5) | the fleet lane: `tools/fleet/first-share-time.sh` |
|
||||
| LG-5 the text | the handoff block (section 4) | the site lane: land it, rebuild, deploy |
|
||||
| LG-11 the customer | the gate line and its check | the project lead: the signature; the execution engineer: the paid job |
|
||||
| LG-13 the prize | the gate line pointing at the staged text | the project lead: escrow, the address, the word |
|
||||
|
||||
## 2. The certificate runbook
|
||||
|
||||
Two enrolments, both in the entity's name, both the day Igneum Labs LTD exists on paper. Neither names the founder publicly: an Apple Developer ID certificate reads `Developer ID Application: Igneum Labs LTD (TEAMID)` and an EV Authenticode certificate reads the entity's registered name; the person who enrols is known to Apple and to the certificate authority, not to the public.
|
||||
|
||||
### 2.1 Apple: the Developer Program as an organisation
|
||||
|
||||
| Item | Value | Source |
|
||||
|---|---|---|
|
||||
| Provider | Apple Developer Program, organisation membership | https://developer.apple.com/programs/ (read 7 October 2026 by lane 5: USD 99 a year, notarisation included) |
|
||||
| Cost | USD 99 a year | the same page |
|
||||
| What the entity supplies | (1) a D-U-N-S number for Igneum Labs LTD at its registered address (free from Dun and Bradstreet; Apple has its own D-U-N-S lookup and request form; a new entity's number can take days to weeks to issue, approximate); (2) the legal entity name exactly as the DIFC registrar records it; (3) a person with legal authority to bind the entity (a director) who enrols with an Apple ID on an entity mailbox (for example `developer@igneum.network`) with two-factor on; (4) a website on a domain the entity controls (igneum.network); (5) a phone number Apple can call for verification | Apple's enrolment requirements for organisations, from memory, approximate; the exact list is on the enrolment page at the time |
|
||||
| What comes out | A Team ID; a `Developer ID Application` certificate (and a `Developer ID Installer` certificate if a .pkg is ever shipped); notarisation through `notarytool` with an app-specific password or an App Store Connect API key | Apple's notarisation documentation, from memory |
|
||||
| Where the key lives | In the login keychain of the Mac that runs `packaging/mac/build-dmg.sh`, exported once to an encrypted .p12 kept with the entity's records; never on igneum-build-1 (CLAUDE.md: the box holds no secret that signs releases) | this lane |
|
||||
| Time | Enrolment review by Apple: days, approximate; D-U-N-S first | from memory, approximate |
|
||||
|
||||
### 2.2 Windows: an EV Authenticode certificate
|
||||
|
||||
| Item | Value | Source |
|
||||
|---|---|---|
|
||||
| Provider | One of the public CAs that issue EV code-signing certificates: DigiCert, Sectigo, GlobalSign, SSL.com (the four this lane knows; from memory). The choice that matters: the CI runner must sign, so pick one whose private key lives in the CA's cloud HSM with a client for Windows (SSL.com eSigner with its CodeSignTool, DigiCert KeyLocker with `smctl`; Sectigo and GlobalSign have hardware-token and cloud options; from memory, approximate). Azure Trusted Signing was considered and set aside: its public-trust tier wants three years of verifiable organisation history (from memory, approximate), which a new DIFC entity cannot show | lane 5 (`reinvent.md` 3.1) for the SmartScreen facts; this lane for the provider notes |
|
||||
| Cost | USD 300 to 700 a year for EV, approximate, from memory of list prices; cloud signing may add a monthly fee or a per-signature count | approximate |
|
||||
| Why EV and not OV | Both are hardware-bound since the CA/B Forum's June 2023 rule (private keys in a FIPS 140-2 level 2 token or an HSM; from memory). EV used to grant SmartScreen reputation at once; SSL.com's page says Microsoft "moved away from automatic instant reputation" (`reinvent.md` 3.1, read 7 October 2026), so even EV may show the interstitial until the file earns reputation. the project lead chose EV; the download page states the two clicks until the 1,000-download mark either way | `reinvent.md` 3.1 |
|
||||
| What the entity supplies | (1) the DIFC certificate of incorporation or commercial licence showing the registered name and address; (2) proof the address is the one on the registrar's record; (3) a telephone number the CA can verify (a public directory listing, or a letter from the entity's accountant or lawyer when there is none); (4) the government ID of the person signing the subscriber agreement and a director's letter authorising that person; (5) the D-U-N-S listing from 2.1, which shortens the CA's organisation check | the EV code-signing guidelines' identity requirements, from memory, approximate; the CA's own checklist governs |
|
||||
| What comes out | A certificate in the CA's HSM (or a USB token posted to the registered address), a signing account for CI with a short-lived credential, and a timestamp URL | approximate |
|
||||
| Where the key lives | In the CA's HSM; the CI credential in the repository's GitHub secrets (rotatable); a USB token, if that route is taken, stays with the Mac and signs through `osslsigncode` with a PKCS#11 module; never on igneum-build-1 | this lane |
|
||||
| Time | Validation 1 to 5 business days after the documents, approximate | from memory |
|
||||
|
||||
### 2.3 Mailboxes and records to create with the entity
|
||||
|
||||
| Item | Why |
|
||||
|---|---|
|
||||
| `developer@igneum.network` (or the name the project lead picks) | The Apple ID of the organisation account and the CA's subscriber contact |
|
||||
| `security@igneum.network` | The disclosure address of the prize (`cryptanalysis.md` 3.3; the project lead's call on the name) |
|
||||
| The D-U-N-S number | Apple requires it; the CA uses it |
|
||||
| An encrypted record of the Team ID, the certificate serials, the expiry dates and the renewal month | Renewal is a calendar item the way a domain is |
|
||||
|
||||
### 2.4 The signing step in the shipper's cut (specification; the shipper ae892a8b0f78fe31c implements)
|
||||
|
||||
Today `packaging/mac/build-dmg.sh` signs ad hoc (`codesign -s -`), the app strips its own quarantine on start, and the Windows exes and installer are unsigned (`.github/workflows/windows.yml`, step "installer"). The step below adds signing where each binary is built and verification where the shipper already verifies, so `tools/ship-app.mjs` keeps its step list (`preflight bump inputs commit ci fetch dmg copy mirror manifest deploy verify console`) and gains checks inside `fetch`, `dmg` and `verify` rather than a new step.
|
||||
|
||||
| Where | What changes | Check |
|
||||
|---|---|---|
|
||||
| `.github/workflows/windows.yml`, step "installer", before ISCC | Sign `igneum-app.exe`, `igneumd.exe`, `igneum-miner.exe`, the window host and the worker exes with `signtool sign /fd SHA256 /td SHA256 /tr <timestamp url>` through the CA's KSP (the cloud client logs in from two GitHub secrets: the account credential and the TOTP or API key the CA gives); then the Inno script's `SignTool=` directive signs the installer and its uninstaller with the same command. The secrets are absent on a fork or a pull request: the step then builds unsigned and marks the artefact `unsigned`, and the shipper refuses it (next row) | `signtool verify /pa /v dist\*.exe` in the same job prints the signer and the timestamp; the smoke-run step reads it |
|
||||
| `tools/ship-app.mjs`, `fetch` | After `packaging/windows/fetch-ci-artifacts.sh`, run `osslsigncode verify <installer>` on the Mac (Homebrew `osslsigncode`): the signer CN must equal `Igneum Labs LTD`, the digest SHA-256, a timestamp present; an unsigned or wrongly signed installer fails the step with the line | the step's own exit |
|
||||
| `packaging/mac/build-dmg.sh` | Replace `codesign -s - -f` with `codesign --force --options runtime --timestamp --sign "Developer ID Application: Igneum Labs LTD (TEAMID)"` on every binary under `Contents/Resources/bin` and `Contents/MacOS`, then the app bundle (with an entitlements file only if a binary needs one; none is known today); `xcrun notarytool submit <zip of the app> --keychain-profile igneum-notary --wait` must print `Accepted`; `xcrun stapler staple` the app, then build the DMG, then `xcrun stapler staple` the DMG; remove the app's own quarantine strip (it exists for the ad-hoc case only) | `spctl --assess --type execute -vv <app>` prints `accepted` and `source=Notarized Developer ID`; `codesign --verify --deep --strict <app>` exits 0 |
|
||||
| `packaging/ota/publish-manifest.sh` and the manifest | Two fields per platform file: `signed_by` (the signer CN as verified) and `notarized` (true or false); `publish-manifest.sh` refuses `--public` when either is missing | `tools/ship-app.mjs --check` and the manifest's signature check already run; add the field check there |
|
||||
| `tools/ship-app.mjs`, `verify` | Besides size and sha256: download the live installer and run `osslsigncode verify`; mount the live DMG and run `spctl --assess`; both results must match the manifest fields | the step's own exit |
|
||||
| The download page (`site/miner.html`, the site lane) and the Discord release post (`tools/community/discord-hooks.mjs release`) | Show "Signed by Igneum Labs LTD" and the sha256 only when the manifest says so; keep the "what your antivirus may say" line (`reinvent.md` 3.1) and the exact SmartScreen text and two clicks until the 1,000-download mark | `tools/ci/launch-gates-check.mjs` greps nothing here; the site's own build grep runs |
|
||||
| `tools/ci/signed-release-check.sh` (new, with the implementation) | A manifest in `dl/public/` without `signed_by` on every file fails; `--self-test` with a manifest missing the field | in `tools/ci/pre-push.sh` |
|
||||
|
||||
Order of work for the shipper once the certificates exist: the Mac side first (one machine, one keychain, one release), then the CI side (the cloud-signing client in the runner), then the manifest fields, then the download page. Until the certificates exist nothing in this table runs, and the 0.4.0 cut ships unsigned with the page saying so (`funding.md` section 2, the client audit row: "the one-click app ships at testnet unaudited and says so on the download page").
|
||||
|
||||
### 2.5 The first-share time, measured per release (specification; the fleet lane builds `tools/fleet/first-share-time.sh`)
|
||||
|
||||
`reinvent.md` 3.1: the time from download to the first accepted share has never been measured end to end on a fresh machine. The measurement is a Devnet 2 gate step, one run per platform per release:
|
||||
|
||||
| Step | What | Where |
|
||||
|---|---|---|
|
||||
| 1 | A fresh Windows 11 VM (Hyper-V on the RTX 5090 Windows rig, or a rented Windows box) and a fresh macOS VM (Tart or UTM on the Mac) from clean images; no Igneum files on them | the fleet lane's box library (`tools/fleet/lib/`) for the rented case; a Mac job for the macOS case |
|
||||
| 2 | The script downloads the release from the public URL, runs the installer (Windows) or opens the DMG and copies the app (macOS), starts the app with the Devnet 2 network setting, and reads the app's own log for three stamps: install done, node synced, dataset built, then the first accepted share or block | the app's log lines are the clock; the script never reads a reported rate |
|
||||
| 3 | One line into the gate log: `FIRST-SHARE <windows or mac> download=<s> sync=<s> dataset=<s> share=<s> version=<v>` | `~/Desktop/fleet/devnet2-gate-<stamp>.log`, the same file `devnet2-gate.sh` writes |
|
||||
| 4 | The numbers go to `/evidence` as a row per release ("download to first share, fresh Windows 11: N min M s; fresh macOS: N min M s"), with the date and the version | the site lane |
|
||||
| Gate | Under 10 minutes in 9 of 10 fresh Windows installs (mission item 6), measured across releases | the /evidence rows |
|
||||
|
||||
## 3. The hash-origin report: what it is and how it runs
|
||||
|
||||
`tools/observer/hash-origin.mjs`. Once a day, from the observer's tables, who found the blocks. It reads `live_blocks` (24 h: vote key, payout address, colour), `live_state` (the node's hash-rate estimate), `miner_logs` (the vote keys the project's intake-reporting workers logged) and, with `--fleet-keys <file>` or `IGNEUM_FLEET_KEYS_FILE`, the fleet registry's key list. It writes, only with `--write`, its own two tables (`hash_origin_days`: one row per key per day for the 30-day dust and independence counts; `hash_origin_reports`: the day's report as posted) and the jsonb column `hash_origin` on `live_state` for the site. With `--post` it posts to #numbers through `tools/community/discord-hooks.mjs`'s poster (its guard refuses any post with a machine id, a path, an IP or a 32-hex token; key `hash-origin:<date>`, so a day posts once).
|
||||
|
||||
| Command | What |
|
||||
|---|---|
|
||||
| `node tools/observer/hash-origin.mjs --fixture tools/observer/fixtures/hash-origin-day.json` | The fabricated day; no database |
|
||||
| `node tools/observer/hash-origin.mjs --dry [--go 2026-10-20]` | The live tables, read-only (ran today) |
|
||||
| `node tools/observer/hash-origin.mjs --dry --prefix dn2_` | The Devnet 2 observer's tables, once that observer runs with `LIVE_TABLE_PREFIX=dn2_` |
|
||||
| `node tools/observer/hash-origin.mjs --write --post --live --go <date>` | The daily run on the box |
|
||||
| `node --test tools/observer/hash-origin.test.mjs` | The known-finished and known-failed days; in `tools/ci/pre-push.sh` |
|
||||
|
||||
Today's read-only reading on the live devnet (7 October 2026, 11:xx UK), and what it means (the consequences rule):
|
||||
|
||||
| Line | Reading | What it means, and what is done about it |
|
||||
|---|---|---|
|
||||
| Keys | 113 with a block, 95 above the dust line, ten largest 45.0 percent | The devnet runs the project's machines with several identities each (ledger-decisions.md decision 13 moves the default to one key per machine), so 113 keys is not 113 miners; the independent count is the X5 definition and its two owed columns |
|
||||
| Fleet | 36 keys, 38.4 percent "fleet"; 77 keys, 61.6 percent "outside" | False: the rented boxes do not upload identity lines, so the job cannot see them as the project's. The fleet registry's key file fixes it (`IGNEUM_FLEET_KEYS_FILE`); until the fleet lane exports it, LG-7 is void and the report says so by its numbers |
|
||||
| Pools | 13 shared payout addresses (68.4 percent); attested pools 0 | A shared payout is a pool or one machine with several identities; the job counts a pool only when attested (the project's own, or an operator's signed key list), so no devnet machine is mistaken for an outside pool and LG-9 cannot pass on a multi-key machine |
|
||||
| Network | 0.76 GH/s, no yesterday | The step line needs two days of its own table; the first run with `--write` starts the series |
|
||||
|
||||
The runbook for the box (build-server lane; `infra/build-server/hands/` holds the observer's units):
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Unit | `igneum-hash-origin.service` (oneshot) and `igneum-hash-origin.timer`, `OnCalendar=*-*-* 08:30:00 UTC` (before the 09:00 UK digest), `Persistent=true` so a missed day runs at boot |
|
||||
| Command | `node tools/observer/hash-origin.mjs --write --post --live --go <go date>` from the box's checkout, as the observer user |
|
||||
| Environment | `EnvironmentFile=/srv/observer/env` (DATABASE_URL) and `/srv/discord-hooks/env` (the webhooks, already on the box by the 6 October exception); `IGNEUM_PROJECT_POOLS=<pool-0 payout address>`; `IGNEUM_KNOWN_POOLS=` (empty until a statement exists); `IGNEUM_FLEET_KEYS_FILE=/srv/observer/fleet-keys.txt` (written by the fleet lane's export, one 64-hex key per line, mode 600) |
|
||||
| Devnet 2 | The same unit with `LIVE_TABLE_PREFIX=dn2_` once the Devnet 2 observer runs; seven days of posts before step 10 is LG-2 |
|
||||
| 90 days | The timer runs for ever; LG-12 counts the first 90 days from the go in `hash_origin_reports` |
|
||||
| Gate | The unit is trusted after one run that posted and one run with the database string removed that failed loudly (the watcher rule) |
|
||||
|
||||
## 4. The text, handed to the site lane
|
||||
|
||||
Everything between the two markers is for the site lane (a4b202cabca2d95c0 deploys). It is written to the copy law and `tools/ci/launch-gates-check.mjs` greps it against `site/forbidden-strings.txt` and `tools/ci/forbidden-strings.txt`. The site lane lands it in `site/index.html`, `site/litepaper.html`, `site/journey.json`, rebuilds (`node site/build.mjs`) and pushes; the ledger owner takes item F into `docs/fud-ledger.md` (`tools/ledger-page.mjs` renders it).
|
||||
|
||||
<!-- handoff:start -->
|
||||
|
||||
### A. The front page: the three wants lead (`site/index.html`, the hero and the first block under it)
|
||||
|
||||
The miners' own words asked for three things (twenty posts and launch texts, 2018 to 2026, `docs/analysis/mission/past.md` section 2: hardware that keeps its value, 10 of 20; income above electricity that does not fall off a cliff, 6; a fair supply, 4). Nobody asked for finality, a DAG or proofs. The front page answers the three in order; finality moves to page two. The h1 stays as it is. Under it, three cards, each with one heading and three to four short lines:
|
||||
|
||||
**1. Your card stays a card.**
|
||||
The hash is a new random program every hour over a dataset the chain draws from its own state. No scheduled fork, no release a team must ship. The chip model and its number are published with every era on /evidence. When you stop, the card still games.
|
||||
|
||||
**2. Income with no cliff.**
|
||||
100 IGN a block, falling 2.9 percent a month. No halving day. Then 1 percent of supply a year, for ever. A block pays its miner whether or not anyone buys a proof that day. What each card mines, and what its electricity costs, is one table: the income page.
|
||||
|
||||
**3. A fair supply.**
|
||||
No premine. No fund, no foundation, no fee to any team. Nobody holds a coin before block one. The founders mine from genesis with disclosed addresses and the same software as everyone else. The first 90 days ramp from 10 percent, so the launch weeks are worth less to a private farm.
|
||||
|
||||
Under the three cards, one line and a link: "Blocks are final by miners alone, with no stake and no other chain. How, on page two." (the link is the litepaper's "Speed and finality" section).
|
||||
|
||||
The income page: render `docs/analysis/income-tiers.md` as `/income` (or link the public mirror's copy) and link it from card 2 and from `/miner`. The page is generated; the site lane does not edit its numbers.
|
||||
|
||||
### B. Page two: finality (`site/litepaper.html`, "Speed and finality")
|
||||
|
||||
No change to the section's text. The change is placement: the front page's hero no longer carries a finality claim; the "Igneum at a glance" block keeps its one finality line and links down to the section. The explorer and the wallet keep their finality state words.
|
||||
|
||||
### C. The block sentence (`site/litepaper.html`, "Three income streams, one balance", directly under the table)
|
||||
|
||||
"A block pays its miner whether or not anyone buys a proof that day. The lottery pays 80 percent of every block from emission; proving is the second income, never the only one. Every useful-work chain on record dropped its miners the day the work stopped paying; Igneum's miners are paid for the block first."
|
||||
|
||||
### D. The eight sentences (`site/litepaper.html`, a new short section "What the chain is, in law" at the end of Economics, under the label "Not legal advice; counsel is engaged")
|
||||
|
||||
Each sentence is `docs/analysis/mission/future.md` 8.3, verified against the file on 7 October 2026 and re-worded to the copy law where the file's own line was a note; the number in brackets is the file's. The heading sentence: "Not legal advice; counsel is engaged. These are the facts of the design that the rules of each region turn on; nothing here is a promotion of anything."
|
||||
|
||||
1. No issuer, no offeror, no sale. Every coin is created automatically as a reward for the maintenance of the distributed ledger or the validation of transactions, and in no other way. (8.3 sentence 1; the quoted words are MiCA Article 4(3)(b)'s, verbatim, as the file asks)
|
||||
2. The sustainability indicators of Delegated Regulation 2025/422 (energy in kWh a year from the network's hash rate and the measured microjoules per hash, intensity per transaction, the regional mix when it is known) are published by the project every era, so a service provider in the EU can list the coin without asking. (8.3 sentence 2)
|
||||
3. No financial promotion. No price, no "buy", no "invest", no return language anywhere. The earnings screen shows hash and IGN, never a currency. (8.3 sentence 3; the income page shows electricity in dollars as a cost, never the coin's price, which is this sentence kept)
|
||||
4. The reference pool never holds a member's balance. Payouts come straight from the coinbase split; the pool coordinates, it does not keep custody. (8.3 sentence 4)
|
||||
5. The job market settles peer to peer on the chain. There is no operator account and no dollar leg in the protocol: the dollar figure is a quote, the settlement is IGN. (8.3 sentence 5)
|
||||
6. The software is published under an open licence by a company that holds no coins by right and runs no service the chain's consensus depends on. There is no dev fund. (8.3 sentence 6; the file's line reads "runs no service the chain depends on"; "consensus" is added because the company does run seeds, a public RPC and a log intake, none of which consensus needs; counsel reads both)
|
||||
7. Mining may be restricted where you are; you are responsible for checking. The miner asks your region at first run and refuses the regions where mining is banned (the file's list on 7 October 2026: ten regions of Russia and Moscow from 15 August 2026, and China). (8.3 sentence 7)
|
||||
8. No privileged key. No key can mint, pause or upgrade the chain; the only way a rule changes is miners signalling for it, and nothing requires them to. (8.3 sentence 8)
|
||||
|
||||
### E. The journey (`site/journey.json`)
|
||||
|
||||
| Phase | Today | Change |
|
||||
|---|---|---|
|
||||
| phase-4, gate | "Finality design passes external review and one rollup signs for testnet" | "Finality design passes external review" |
|
||||
| phase-5, gate | "1,000 independent miners run 30 days and rollup proofs are delivered on time" | unchanged |
|
||||
| phase-6 (mainnet), when | "After the testnet has passed its gate: 1,000 independent miners for 30 days and rollup proofs on time" | "After the testnet has passed its gate (1,000 independent miners for 30 days, rollup proofs on time) and one proving customer has signed: a customer paying for proofs at a published rate, or a letter of intent with a volume" |
|
||||
|
||||
### F. The ledger rows (`docs/fud-ledger.md`; ids provisional, the ledger owner renumbers on merge)
|
||||
|
||||
**X13 (existing), status update.** Decided (7 October 2026, 10:1x UK, by the owner): a signed proving customer, paying for proofs at a published rate or a letter of intent with a volume, is a MAINNET gate (testnet-go.md LG-11). The phase 4 gate drops "one rollup signs for testnet". The pilot progression in the answer stands: a signature opens mainnet, repeat purchases and a second unrelated customer are the evidence after it.
|
||||
|
||||
**X37. Your installer is a warning screen.**
|
||||
"Windows says 'Windows protected your PC' and the Mac refuses to open it. A miner's first screen on your chain is an operating-system warning, and you call that one click."
|
||||
Status: Open, certificates approved (7 October 2026): an EV Authenticode certificate and an Apple Developer organisation account, both in the entity's name, enrolled the day the entity exists; the signing step is specified for the release tool and runs from the first signed cut. Until then the download page shows the exact warning text and the two clicks.
|
||||
Answer: True today. The DMG is signed ad hoc and the Windows installer is unsigned. SmartScreen warns on any file without reputation and reputation is use, so the interstitial may outlive the signature by some downloads; the page says so. The measured time from download to the first share on a fresh machine is published per release.
|
||||
Evidence: testnet-go.md LG-3 and LG-4; launch-pack.md section 2.
|
||||
|
||||
**X38. Nobody will know whose hash the launch is.**
|
||||
"On launch week your own rented cards will be most of the hash, and the chart will look like Nervos in 2020 or Iron Fish in 2023: a step nobody can explain."
|
||||
Status: Open, the report built (7 October 2026): a daily hash-origin report from the observer, posted in public for the first 90 days and on the staging chain for seven days before the go: keys with a block, keys above dust, the project's own fleet share, attested pools, the ten largest keys, and any 2x step in the estimate with the keys that carry it.
|
||||
Answer: Right that the first weeks are the project's own cards plus whoever shows up, and that a chart alone cannot tell them apart. The report names the project's share every day, from the project's own key list, so the step is explained the day it happens. What it cannot do yet: count independent miners (the autonomous-system and fingerprint columns are owed), or tell a pool from a machine with several keys without the pool's own signed statement.
|
||||
Evidence: testnet-go.md LG-2, LG-6, LG-7, LG-9, LG-12; tools/observer/hash-origin.mjs.
|
||||
|
||||
**E23. You will show a number that makes someone buy a card.**
|
||||
"Every GPU chain's launch page had an earnings number, and every one was wrong inside six months."
|
||||
Status: Stated (7 October 2026): the income page shows IGN a day per measured card at three network sizes, electricity a day at three tariffs, and the electricity cost of one mined IGN; no coin price appears, every rate is a measurement with its source, and the page says what each tier should expect from the record of other chains.
|
||||
Answer: Agreed, and the record is the argument: income halves within 60 to 120 days of a peak and falls under power within 6 to 18 months on every chain in the sample. The page shows the arithmetic the miner can redo with the live network figure, and nothing else.
|
||||
Evidence: docs/analysis/income-tiers.md; testnet-go.md LG-1.
|
||||
|
||||
**L10. The eight sentences.**
|
||||
"You say 'not legal advice' and then write eight sentences of it."
|
||||
Status: Open, counsel engaged (the owner, 6 October 2026); the eight sentences are the design facts the rules turn on, each cited to its regime in the research file, and they go to counsel with the litepaper before mainnet.
|
||||
Answer: The sentences state what the design does (no issuer, no custody in the pool, no dollar leg, no privileged key, the region refusal) and what the project publishes (the energy indicators); they do not say what any law concludes. Counsel decides the wording for each region.
|
||||
Evidence: docs/analysis/mission/future.md section 8; testnet-go.md LG-5.
|
||||
|
||||
<!-- handoff:end -->
|
||||
|
||||
## 5. What needs the project lead
|
||||
|
||||
| # | Item | What exactly | Blocks |
|
||||
|---|---|---|---|
|
||||
| 1 | The entity's documents | Igneum Labs LTD's certificate of incorporation or DIFC commercial licence, proof of the registered address, a director's government ID, a director's authorisation letter naming who enrols and signs | LG-3 (both certificates), LG-13 (the payer's address on the prize text) |
|
||||
| 2 | The D-U-N-S number | Request it for Igneum Labs LTD at the registered address (free; days to weeks, approximate) | the Apple enrolment; the CA's organisation check |
|
||||
| 3 | The Apple Developer Program enrolment | As an organisation, on an entity mailbox with two-factor, USD 99 a year, the project lead as the person with authority; Apple may call | LG-3 (the Mac side) |
|
||||
| 4 | The EV certificate order | Pick the CA (a cloud-HSM one so the CI runner signs: SSL.com eSigner or DigiCert KeyLocker, approximate), pay (USD 300 to 700 a year, approximate), take the validation call, receive the signing account; the CI credential goes into the repository's secrets | LG-3 (the Windows side) |
|
||||
| 5 | The proving customer's signature | One customer paying for proofs at a published rate, or a signed letter of intent with a volume (Taiko is the named first customer; the brief is ledger X13's) | LG-11, mainnet |
|
||||
| 6 | The prize | Escrow USD 50,000 with the entity; confirm the registered address on the staged text; give the publish word; pick the disclosure mailbox name | LG-13 |
|
||||
| 7 | Two mailboxes | `developer@igneum.network` (or the project lead's name for it) and `security@igneum.network` | items 3, 4 and 6 |
|
||||
| 8 | The three tariffs | DECIDED (the coordinator for the project lead, 7 October 2026, 11:xx UK): electricity tariffs per kWh, never a coin price; the triple is USD 0.05 (cheap industrial), 0.10 (US retail) and 0.25 (UK retail at today's rate), and the table was regenerated on it. Nothing left here | none |
|
||||
| 9 | Permission to post the first outside block | reinvent.md 4.1 G-C2: the "first outside block" post needs the owner's permission | LG-7's post, not its check |
|
||||
|
||||
## 6. Not done here, and why
|
||||
|
||||
| Item | Why not | Who |
|
||||
|---|---|---|
|
||||
| Any change to a live page or to `site/` | The site lane deploys; this lane hands text (section 4) | site lane |
|
||||
| The signing step's code | A specification only (2.4); the shipper implements when the certificates exist | shipper |
|
||||
| `tools/fleet/first-share-time.sh` | A specification only (2.5); it needs the fleet library and a Windows VM job | fleet lane |
|
||||
| The Devnet 2 observer and the fleet key file | Fleet-side; without them LG-2 and LG-7 cannot fire | fleet lane |
|
||||
| The systemd timer on the box | Box-side; the unit is specified in section 3 | build-server lane |
|
||||
| The X5 observer columns | The app-owner item of ledger-decisions.md section 3 | observer owner |
|
||||
| Ledger edits | Handed as rows (item F); the ledger owner merges and renumbers | ledger owner |
|
||||
| The litepaper artifact (the Claude doc) | The site's `litepaper.html` is the public copy the handoff targets; the doc follows the site | site lane |
|
||||
114
docs/plans/ledger-decisions.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# FUD ledger: decisions for the project lead
|
||||
|
||||
Written 5 October 2026, night, by the ledger closer (branch `fud-close`). One paragraph per ledger item that cannot close without the project lead: the question, the facts the ledger already holds, the recommendation, and what the decision unblocks. The ledger entry for each says "Decision owner: the project lead" and points here. Nothing here is decided until the project lead says so; when he does, the ledger entry gets the dated "Decided" line and this file keeps the paragraph with the outcome appended.
|
||||
|
||||
Items owned by other agents tonight (C4, M20, F21 and F22 with the N3 switch, the transaction relay, GPU hot-plug) are not here.
|
||||
|
||||
## 1. M1 and M22: the ASIC challenge, its terms, judge and funding
|
||||
|
||||
Question: what the standing bounty scores, who judges it, what it pays and who pays. Facts: M1's "under 2x" is a hash-rate target with no scoring rule; M22 lists the metrics the benchmark should publish (hashes per second and per joule per program over at least 100 epochs, reported as worst decile and median, never one average; capital cost per unit of hash rate at a stated volume; a longevity term against the instruction-family reserve and the dataset growth of spec 1.13; recomputation, partial storage and weak-program selection scored separately); M16 prices the recompute attacker at 1.5x to 2.4x at equal integer budget and 3x to 6x with a fixed-function factor, with the mixer-cost lever that brings it under 1x at zero honest cost. Recommendation: publish the scoring rules with the January 2027 benchmark exactly as M22 lists them; the payer is the entity (Igneum Labs LTD), never a person; the judge is a named external reviewer paid from the review line of the funding table (item 4 below), not the team; the reward is a fixed sum in fiat announced with the rules; eligible hardware is any design with a public bill of materials and a reproducible simulation or a working part; the public claim until a design has been scored is the one M22 words ("consumer GPUs remain competitive against the best independently proposed specialised design across the tested workloads and the stated economic assumptions"). Unblocks: the M1 status moves from "Open, target" to "Open, bounty terms published, unclaimed since <date>", and the litepaper's bounty sentence gets a date.
|
||||
|
||||
## 2. F16: a lock that can become uncertified after a heal (gate 3, O-3.17)
|
||||
|
||||
Question: option A (spec 3.5 as first proposed: strike the equivocators, re-evaluate, uncertify the index if neither or both lock) or option B (spec 3.11.4: a verified certificate is never withdrawn, the node reports the conflict, finality pauses until an operator resolves it with a trusted certificate). Facts: the ledger's F16 table prices both; the state needs a 34% equivocator or a partition longer than a window; under option A every lock in that state (2 to 69 indices in the simulations, 23 on the cloud devnet) was reported locked and then withdrawn, which makes a lock a confirmation count; under option B no reported lock is ever withdrawn and the price is an operator-length pause. Note: the `c4-fix` branch (another agent, tonight) rewrites spec 3.5 with the certificate-driven reorg rule for C4; that rule is about following a certificate over a block off the node's chain and does not decide F16, but the two paragraphs sit in the same section, so the F16 text should be written after `c4-fix` merges. Recommendation: option B, as the ledger already recommends; it is Kaspa's rule for a finality conflict and the only reading under which an exchange can credit on a lock. What it needs after the decision: the 3.5 paragraph replaced by 3.11.4's text, `finality_conflict` and the `finality_active` clear in the node, the forced double-certificate test of 3.11.7. Unblocks: F16 moves to "Decided, fix scheduled"; the node work is one consensus-engineer item.
|
||||
|
||||
## 3. X5 and X14: what "independent" means for the 1,000-miner gate (O-X.1)
|
||||
|
||||
Question: adopt the definition the evening sweep wrote. Facts: the unit is a vote key above the dust count in the 30-day window; two keys are independent when they differ in all three of the autonomous system of the announcing address, the machine fingerprint the miner app sends with its log uploads, and the pool attestation; N_ind is the number of distinct classes; the gate reads "N_ind >= 1,000 over 30 days with top-10 share of window weight under 50%". Tonight's reading: 21 keys above dust are 5 machines (4.2 keys per machine) and at most 3 autonomous systems. Recommendation: adopt it as written, with one addition: a key whose machine fingerprint is absent (a miner that sends no logs) counts as its own class only if its address is in an autonomous system no other key uses, so a silent fleet cannot inflate the count. What it needs: the observer stores the autonomous system per announcing address and the fingerprint per key, and a pool statement format (a signed list of keys per pool operator). Unblocks: X5 moves to "Decided, measurement scheduled", the observer columns become one app-owner item, and the phase 5 gate in `site/journey.json` gets the definition.
|
||||
|
||||
## 4. E14: the funding table
|
||||
|
||||
Question: whether `docs/plans/funding.md` leaves PLACEHOLDER status, and which lines are published. Facts: the fund was removed by design (E4); the sources are the founder's own means today, the 1% client fee, the team's own mining, proving and apps after mainnet, no protocol fee; the cost lines in the plan are approximate estimates (a contracted cryptographer USD 80,000 to 150,000, the finality review USD 50,000 to 100,000, the node audit USD 60,000 to 120,000, from memory, approximate). Recommendation: keep the table internal until counsel has read it (L1, L2), but publish one sentence in the litepaper now that names which lines are unfunded (the second client, the external reviewers, the bounty), because E14's critic is right that "no fund by design" without a table reads as no plan. Unblocks: E14 moves from "Open, placeholder" to "Decided: internal table, public unfunded-lines sentence", and G1, G5 and M22 can point at the funded line that pays them.
|
||||
|
||||
## 5. X13: the first paying proving customer, timing and terms
|
||||
|
||||
Question: whether the paid pilot moves from phase 5 to before the public testnet, as the external reviewer asked, and on what terms. Facts: the phase 4 gate is a signed letter of intent with no payment; the brief now carries the progression (agree workload, proof format, deadline, failure rate and price before the pilot; publish the outcome and the customer's own reason; repeat purchases without reimbursement; then several unrelated customers); payment before launch needs the entity, terms and tax treatment of L4. Recommendation: keep the phase 4 gate as the signature, put the paid pilot in phase 5 as written, and do not move it earlier until counsel has answered L4; a paid pilot before the entity's terms exist is the thing L4 warns against. Unblocks: X13 stays "Open, experiment scheduled (the pilot)" with a dated phase and no earlier promise in public text.
|
||||
|
||||
## 6. L1, L2, L4, L5: counsel and the trademark search
|
||||
|
||||
Question: engage counsel in the entity's jurisdiction (DIFC) for the Howey and promotions review of the founder-business paragraph and the launch grants (L1, L2), the testnet payment arrangement (L4), and record a trademark clearance search in classes 9, 36 and 42 at EUIPO, USPTO and the UK IPO (L5). Facts: nothing is runnable by an agent; the ledger's "offshore, parked" is now an entity with an address (Igneum Labs LTD, DIFC); the inducement wording is being removed from the litepaper tonight (L2 text half, group A); the trademark search of 3 October 2026 (recorded outside the repository) found IGNIUM UK00918212492 as the obstacle. Recommendation: one engagement letter covering all four, before litepaper v0.2 and before any public repository; the trademark result recorded in the repository as a dated one-line entry (found or clear per register) with the search itself kept outside. Unblocks: L1, L2, L4 move to "Open, counsel engaged <date>"; L5 moves to "Searched <date>: <result>".
|
||||
|
||||
## 7. L3: the registrar move
|
||||
|
||||
Question: the deSEC nameserver move has started (a token sits in `~/.config/igneum`, mode 600); the Vercel records must be recreated at deSEC and every domain re-verified in the Vercel project; the registrar move waits for the transfer lock to end in December 2026. Recommendation: do the nameserver move in one sitting with every domain's Vercel verification checked afterwards, because a half-moved zone takes the site down; the December registrar choice is the project lead's (a non-US registrar that accepts the entity). Unblocks: L3 moves to "Mitigated in part (nameservers, <date>); registrar December 2026".
|
||||
|
||||
## 8. G14: the history rewrite date
|
||||
|
||||
Question: when the rewrite of `docs/plans/history-rewrite.md` runs. Facts: 291 of 363 commits carry the +0100 offset, 40 carry the personal name, the intake key is in 6 tracked files across 8 commits and the dl token in 1; the dry run on a throwaway mirror is done; the rewrite breaks every open worktree and branch and so must run when no agent is mid-work. Recommendation: the morning after the last of tonight's branches merges, with every worktree removed first and both secrets rotated regardless; the rewrite is a precondition of the public repository (fud-fixes section 5), not of the testnet. Unblocks: G14 moves to "Scheduled <date>".
|
||||
|
||||
## 9. X29: the live node's RPC on every interface
|
||||
|
||||
Question: `igneumd` on this Mac listens on `*:26610` so that PC 2 can reach it. Facts: the file modes are fixed; the live node is read-only for agents tonight. Recommendation: `--rpclisten=127.0.0.1:26610` on the Mac node and PC 2 on its own node (it runs one for mining already) or an SSH tunnel; done by the operator at the next planned restart of node 1, never mid-run. Unblocks: X29 closes once the restart is logged.
|
||||
|
||||
## 10. P9: the shard-market parameters (O-5.1, O-5.6)
|
||||
|
||||
Question: the values of the 8 assignees, the exclusive window (10 DAA s today, 25 s proposed), the job claim timeout (120 s proposed by the economy simulator) and whether external jobs carry a bond. Facts: the parameter table is written from the live numbers (P9 sweep); the simulator found the claim timeout a market parameter and recommends starting the devnet at 120 s; shards carry no bond since the sortition rule. Recommendation: take the table as the phase 4 devnet's starting values (8 assignees, 25-s window, 120-s claim timeout, no shard bond, external job bond set on the devnet) and let the devnet measurement move them; nothing in public text names a value until then. Unblocks: P9's "parameter table written" becomes "values set for phase 4".
|
||||
|
||||
## 11. P21: the SP1 verifier in consensus
|
||||
|
||||
Question: whether the node carries the SP1 SDK (the verifier inside consensus) or a bounded in-consensus verification budget, or stays on v0 (every producer verifies off the consensus path) through the public testnet. Facts: on v0 the native-execution veto stops any wrong state; the damage of an unverified record is one prover's payout; the live devnet runs v0 with every producer verifying. Recommendation: stay on v0 through the public testnet and state it in the litepaper's proving section with the label Open, because carrying the SDK in the node is a dependency decision (size, build time on the PCs, the audit surface) that belongs to the execution engineer's plan and not to a night fix. Unblocks: P21 stays "Open, stated in spec 7.7 item 4" with the public testnet as the next date rather than no date.
|
||||
|
||||
## 12. M8, M11, P16: hardware the measurements need
|
||||
|
||||
Question: whether to buy or borrow a discrete AMD card (M8: bit-exactness and the honest rate on RDNA, the one vendor not yet run), a multi-card mixed-generation rig with ROCm (M11: hourly runtime codegen on the rig a farm runs), and a 12 GB mid-range NVIDIA card (P16: the phase 2 proving gate end to end on the card the gate names). Facts: every other vendor and machine class has run (Apple, NVIDIA discrete, AMD integrated, Intel integrated); the ledger's answers on these three items are honest about the gap and nothing an agent can run on this fleet closes them; the public benchmark in January 2027 will also need them for the leaderboard. Recommendation: one discrete AMD card (a 16 GB RDNA 3 or 4 part, approximate class) and one 12 GB NVIDIA card (a 3060-class part) bought for PC 2 before the public benchmark; the multi-card rig borrowed from a farm operator for a week at the HiveOS package's first test rather than bought. Unblocks: M8 and P16 move to "measurement scheduled <date>"; M11 moves to "rig borrowed <date>".
|
||||
|
||||
## 13. F3, F17, X5: the three gate-3 parameters proposed in spec 3.4.2 (round 2)
|
||||
|
||||
Question: adopt, at gate 3, the three values the ledger-tails round wrote into `docs/spec/03-finality.md` section 3.4.2 as Proposed. Facts, from the fork's encodings and the live devnet's coinbase sizes (3.4.2 item 1): a vote item is 281 bytes, so a checkpoint's 8,192 votes at the S2 switch are 2.3 MB, 4.6x one block's compute mass, and no per-block bound lets one block carry a checkpoint; spread over the 30 blocks of a checkpoint interval the average is 274 votes per block (15.4% of the mass). The bitmap indexes the canonical voter list at one bit per key: 1,024 bytes at 8,192 voters against a 1 MiB wire bound today. The client defaults to 8 identities per large card, 2 per small, 1 on Apple silicon and integrated GPUs, which is why tonight's fleet runs 4.2 vote keys per machine. The hostile-aggregator simulation (scenario O, 3 seeds) shows the attack works under the certificate reading the simulation used until tonight and does nothing under the block reading spec 3.3 Q2 fixes. Recommendation: (a) the per-block vote bound at the value item 2 proposes, sized so a checkpoint's votes fit in its interval with headroom; (b) the bitmap wire bound at 8,192 bytes (65,536 voters, 8x the switch) in place of 1 MiB; (c) the client default of one vote key per machine (not per card or per identity), with the identity count kept as a worker setting that shares the key. Unblocks: O-3.3, O-3.5 and O-3.12 move from Proposed to Decided; the F17 and X5 Sybil arithmetic then rests on a default that matches the rule.
|
||||
|
||||
## Outcomes (6 October 2026, 17:25 UTC, the project lead's decisions, relayed by the coordinator)
|
||||
|
||||
| Item | Decision | Ledger lines written |
|
||||
|---|---|---|
|
||||
| 1 (M1, M22) | CORRECTED at 17:35 UTC: NO device bounty (the 17:25 tiers of USD 250,000 and USD 100,000 are withdrawn: a team with a real 2x chip earns more mining than any bounty, so those tiers attract nobody). The claim "under 2x" is backed by the paid independent cryptanalysis (the Monero route: four paid reviews, no bounty) and the public benchmark with M22's metrics. Optional, the project lead's call later: a single cryptanalysis prize of USD 50,000 for a published 2x+ shortcut in the mixer, the chained cache or the acceptance rule, escrowed before it is named. Every public mention of a bounty is struck from the litepaper, the evidence page, spec 06 O-1.17 and the funding plan (this commit); the Counter ASIC 2.0 document's USD 20,000-a-day issuance trigger on the `ca2-coord` branch now points at the paid cryptanalysis and the benchmark's next round (applied there in commits 6141e01 and 8c5b02f, 6 October 2026, with every other bounty sentence on that branch struck) | M1, M22
|
||||
| 2 (F16) | YES, option B | F16 |
|
||||
| 3 (X5, X14) | YES as written, with the silent-fleet addition | X5, X14 |
|
||||
| 4 (E14) | YES: internal table, one public unfunded-lines sentence | E14 |
|
||||
| 5 (X13) | YES: the pilot stays in phase 5 | X13 |
|
||||
| 6 (L1, L2, L4, L5) | IN PROGRESS: counsel engaged | L1, L2, L4, L5 |
|
||||
| 7 (L3) | YES | L3 |
|
||||
| 8 (G14) | YES: the morning after the last branch merges | G14 |
|
||||
| 9 (X29) | YES at the next planned node 1 restart: localhost bind, the wallet's node too | X29 |
|
||||
| 10 (P9) | YES | P9 |
|
||||
| 11 (P21) | YES: v0 through the public testnet | P21 |
|
||||
| 12 (M8, M11, P16) | DONE 6 October 2026: a 9070 XT and a 4070 on order; the mixed rig borrowed later | M8, M11, P16 |
|
||||
| 13 (F3, F17, X5 spec 3.4.2) | YES | F3, F17 |
|
||||
|
||||
Public text that follows from these and is not yet written (next round): the unfunded-lines sentence in the litepaper Economics (item 4); spec 3.4.2 moved from Proposed to Decided and spec 3.5 replaced by 3.11.4's text after `c4-fix` merges (items 2 and 13).
|
||||
|
||||
|
||||
## Standing decisions (6 October 2026, 22:3x UK, the project lead, at the Horizon close)
|
||||
|
||||
Recorded by the Horizon closer (branch `horizon-close`) from the project lead's three lines of 22:3x UK, verbatim first, meaning after. The one page is `docs/analysis/horizon-2026-10.md` section 1; the measurements are lane 4 (`docs/analysis/horizon/economy-and-utility.md`) and lane 6 (`docs/analysis/horizon/polish.md`).
|
||||
|
||||
| Line, verbatim | Standing decision |
|
||||
|---|---|
|
||||
| "Fees cannot fund security for a decade" | Fee revenue is never assumed as the security budget in any model or public sentence. Lane 4 measures fees at USD 450 a day at launch and USD 4,200 a day in year 5 against USD 54,800 and 13,700 of daily emission, so fees stay small for a decade or more. Self-sustaining means the emission curve keeps mining worth doing on its own for as long as fees are small; emission never decays on a schedule that assumes fees take over. the project lead rejected "first decade" as a bound: there is no end date on emission carrying security. |
|
||||
| "Miners need to be the security" | Miners are the security always: no time bound, no stake, no outside checkpoints, no committee, no external security of any kind in the design (the 3 October rulings against stake and Bitcoin anchoring stand). The chain pays its own miners from emission plus fees; nobody pays upkeep, not the founder, not a treasury, not a dev fund. |
|
||||
| "Wrong constants and claims in our own text" | the project lead's acknowledgement of lane 6's finding (polish.md: the public text carried constants and claims that did not match the spec or the measurements). The fix is the nine ledger rows M32, M33, F26, E19, E20, G15, P24, E21, P25, each Conceded and stated on master on 6 October 2026, plus X31 to X33 (the testnet date, the roadmap months, the benchmark month). |
|
||||
|
||||
Still owed from the project lead after the close: the cryptanalysis spend (funding.md: two independent reviews, USD 80,000 to 160,000, before the testnet genesis); the testnet date word (the site says "weeks away"); the N ladder at genesis (the era-draw ladder of the algorithm lane, each step by 90 percent signal); the activation of the verification switch `proving_consensus_verify_daa` (off by default in 0.3.16).
|
||||
|
||||
## Decisions (6 October 2026, 23:2x UK), the project lead's answers to the seven questions
|
||||
|
||||
| # | Question | the project lead's word | Meaning |
|
||||
|---|---|---|---|
|
||||
| 1 | Emission shape | "As i said, find a solution" | The tail is the baseline; the economy lane models the revolutionary candidates (thermostat, settled-value targeting, the hybrid) and the supply, coins-per-block and halving comparison against Kaspa and the field; nothing that penalises a holder; the recommended shape ships as genesis parameters with code. |
|
||||
| 2 | Latency-shadow ladder | Explanation requested | Answer pending; the six-step ladder, every step by 90 percent signal, never unconditional, verifier-bounded at 10 ms, is being implemented behind its switch meanwhile. |
|
||||
| 3 | Proof verification in consensus | On | `proving_consensus_verify_daa` = 0 in the testnet genesis. Devnet stays off. |
|
||||
| 4 | Finality leave item | On | `finality_leave_activation_daa` = 0 in the testnet genesis. Devnet stays never until the 95 percent signal. |
|
||||
| 5 | Base unit | 18 | 18 decimals on the testnet (O-2.6 Decided, pending the implementation lane's gate); the devnet keeps 8. |
|
||||
| 6 | Cryptanalysis spend | Yes | An outside team attacks the hash class after class v4 has run a week of real hash; bounded (lane 2's USD 80,000 to 160,000); the procurement plan is owed. |
|
||||
| 7 | Testnet date | Leave open | No month anywhere; the go checklist is the date. |
|
||||
|
||||
Standing rulings of the same hour: "We dont want to penalise holders" (dormant-coin rent and anything that takes from a balance or taxes inactivity is refused for ever); vote-or-burn is implemented only if 95 percent or more of the mining community would respect it (the weigh-up lane decides between the burn and the signing bonus).
|
||||
|
||||
## Decisions (7 October 2026, 09:3x UK), the project lead's approvals
|
||||
|
||||
| # | Decision | the project lead's word | What it sets |
|
||||
|---|---|---|---|
|
||||
| 1 | Emission schedule | Approved | The testnet genesis carries `EmissionSchedule::TESTNET_1`: 100 IGN a block at 1 bps, a monthly glide with a two-year half-life, a 90-day ramp from 10 percent, a tail of 1 percent of supply a year from the month the glide first pays under it (about year 11.4). No hard cap; the public sentence is the one in docs/analysis/tail-emission.md. Ledger E22 moves to Decided. The devnet keeps `CURRENT`. |
|
||||
| 2 | Signing bonus at the testnet genesis, no burn | Approved | `signing_bonus_activation_daa` = 0 and `signing_bonus_bps` = 1,000 in the testnet genesis; the unsigned tenth to the proving pool; the vote-or-burn code leaves the tree before the testnet code is public. |
|
||||
| 3 | The latency ladder | Approved | The six rungs (27, 35, 53, 88, 173, 267 passes), rungs 0 to 2 admissible, rung 3 re-measured on a quiet core before genesis, 4 and 5 inadmissible until verifiers allow; every step by 90 percent in each of seven windows, never unconditional; `latency_ladder_activation_daa` = 0 on the testnet at rung 0. |
|
||||
| 4 | Cryptanalysis | Approved, "make sure they find ZERO flaws, also cut costs if possible" | The engagement runs at the low point (about USD 80,000) unless a quote forces more; an internal attack pass precedes it so the firms find nothing new; every finding is fixed before the testnet go. The contracting entity and the prize are still the project lead's to confirm. |
|
||||
| 5 | Re-cut the testnet genesis | Approved | One cut with 18 decimals, `TESTNET_1`, and the switches on from genesis: proof verification, the leave item, the signing bonus, finality v3, the ladder at rung 0. Nothing live is touched; the go checklist decides the date. |
|
||||
283
docs/plans/miner-ui-3-audit.md
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
# Igneum Miner UI 3: the audit, 6 October 2026
|
||||
|
||||
the project lead, 18:00 UTC: "run another audit of the miner app and make it as gorgeous as possible and as simple as possible but
|
||||
not lacking any features, think of this as an Apple product in terms of UI and UX, with tuning easy, buttons to tune
|
||||
each card again, or if automated then fair enough." Branch `miner-ui-3`, worktree `../igneum-wt-miner-ui-3`, from
|
||||
master b79f2cb. The design is `docs/plans/miner-ui-3.md`; this file is what was found.
|
||||
|
||||
## 1. How it was looked at
|
||||
|
||||
| What | How |
|
||||
|---|---|
|
||||
| The installed 0.3.13 app on this Mac | its URL from `~/Library/Application Support/Igneum/app/app.url`, opened in the built-in browser pane and shot headless with Playwright's own chromium (never Chrome.app). The miner is paused on the project lead's order and was not resumed; `/api/state` read only. |
|
||||
| The states this Mac cannot show (an NVIDIA rig, syncing, a node restart, a clock block, no cards, the setup screens) | `tools/ui-mock/server.mjs` on port 4317 from this worktree (the one on 4310 is another agent's and serves the old one-page UI; do not read it as master), scenarios `fresh`, `nvidia`, `syncing`, `nodedown`, `nocards`, `clock`. |
|
||||
| The update card and the strips | the UI's own `?update=` and `?job=` sample states on the live app (client side only, nothing posted). |
|
||||
| Sizes | 1280 x 820 (the shipped window), 900 x 600 (the hosts' floor), 390 x 844 (a narrow Windows window, a phone). |
|
||||
|
||||
Screenshots: `docs/plans/miner-ui-3/00-*.png` to `26-*.png`, named below. The live app's state while shooting: Apple
|
||||
M5 Max, paused, node synced at 109,859 blocks with 6 peers, 23,056 lifetime blocks, proving off, external node.
|
||||
|
||||
## 2. What a first-time home miner sees, screen by screen
|
||||
|
||||
Rating: 10 = nothing to change. The three questions per screen: what does it say in jargon, which number has no
|
||||
unit or no meaning, which 0 or blank has no reason word.
|
||||
|
||||
### 2.1 Welcome (`00-welcome.png`), 7/10
|
||||
|
||||
Good: one heading, one lead, three tiles, one button. The eyebrow "devnet v4 · nothing is bought or sold" is honest.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "Mined by GPUs. Proven by fire." is an aphorism (copy law). | lead |
|
||||
| "Syncs the chain from the seed node", "A new program every hour. The next one compiles while you mine", "EVM address", "seed" (the last line) are jargon a home miner has never met. | the three tiles, the seed line |
|
||||
| The coin is the retired ringed coin (`brand/README.md`: "the old ringed coin. Retired as a source"). The app icon is the plain square. | the hero |
|
||||
| The welcome has no idea what the user will earn or spend; the one question a home miner has ("is this worth running?") is unanswered until the third screen. | whole screen |
|
||||
|
||||
Apple would: keep one line of what it does, three tiles in plain words (runs in the background, uses your graphics
|
||||
card, pays an address you own), the button. Drop the seed line (there is no seed phrase in this app; it is a key).
|
||||
|
||||
### 2.2 Set up, step 1, your GPU (`01-setup-gpu.png`), 7/10
|
||||
|
||||
Good: one row per card, the switch, a sentence. The integrated-GPU rule is explained.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "worker Metal", "40 GPU cores", "64 GB unified": three facts, none the user chose or can change. "worker" is internal. | the row's meta line |
|
||||
| "Each card you switch on gets its own worker" is engine talk. | the help note |
|
||||
| The NVIDIA note about the 80% cap and the administrator prompt appears only when an NVIDIA card is present; that is right, but it is 44 words for a decision the user is not taking here. | `#cards-power` |
|
||||
| No number: the row does not say what the card is expected to make. The fleet priors (`site/miner-priors.json`) know the model's MH/s and W; the row could say "about 120 MH/s at 290 W" for a known model. | the row |
|
||||
|
||||
Apple would: one row, name, switch, one line under the list. The cap note goes to the card where it applies.
|
||||
|
||||
### 2.3 Set up, step 2, where rewards go (`02-setup-address.png`), 8/10
|
||||
|
||||
Good: two options, one recommended, the paste box opens on choice, one button.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "EVM address", "secp256k1 key", "0x followed by 40 hex characters": the first is unavoidable once, the second is jargon that helps nobody. | the lead, option 1 |
|
||||
| The dev-fee sentence sits here, on the address screen, as a surprise. It belongs where the fee is switched (Settings) and on the earnings line. | the lead |
|
||||
| "Start mining" on this screen does not start mining when "Make me an address" is chosen: the key sheet comes first. The button lies once. | the primary button |
|
||||
|
||||
### 2.4 The key sheet (`03-setup-key.png`), 8/10
|
||||
|
||||
Good: shown once, both values with Copy, the file path, the checkbox gate.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "On devnet the vote keys are test keys derived from the miner's label. Mainnet vote keys will be random and stored like this wallet." is an internal note on a user's first sheet. | the second note |
|
||||
| "Settings can show it again": the key lives on Rewards, not Settings (`r-key-card`). Wrong page named. | the first note |
|
||||
| The address box repeats what the next screen shows; the sheet could show the key alone, with the address beneath in small. | the two boxes |
|
||||
|
||||
### 2.5 Mine (`04-mine-live.png`, rig: `15-mine-nvidia-rig.png`), 6/10
|
||||
|
||||
What a home miner sees first after setup. Good: the big button, the rate, the rows, the blocks strip.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| Four tiles compete with the big button: hash rate, blocks found, next program. "Next program 49:52 · next program not known yet (…)" is a countdown to an internal event, cut off mid-sentence. A home miner does not need an epoch timer on the first screen. | hero row |
|
||||
| The card row's numbers are `n/a` three times on this Mac (hash rate, temperature, power), in ember, which reads as an error. Apple silicon reports no temperature or power, so the words should be "not reported on Apple silicon", once, not three red `n/a`. | GPU row |
|
||||
| The row says "off · 64 GB unified" while the switch is ON and the big button says "Start mining": three states on one row. The card is on; the miner is paused. The row needs the word "paused". | GPU row |
|
||||
| 0.0 MH/s in ember with "paused" under it: the number is right, but the eye reads a red zero. The rule (the project lead, 6 October 2026, ember-tune.md 6a): a card never shows 0 MH/s without a reason word. The tile does carry "paused", so it passes, but in grey, under a red 0. | hash rate tile |
|
||||
| No money anywhere. The page shows MH/s, W, °C, blocks. It never says what a block is worth, what the electricity costs, or what the machine made today. The one number a home miner wants is missing. | whole page |
|
||||
| MH/W (efficiency) is on Settings, not on the row where the rate and the watts are. | GPU row |
|
||||
| "Your blocks": "last 10 min 0 · last hour 0 · dev fee 0 · chain 109.8k". Four bare counts; "chain 109.8k" is the chain height with no label of what it means; "dev fee 0" is the count of blocks mined for the dev fee, with no unit word (the tooltip has it). | blocks card head |
|
||||
| The blocks strip is 170 px of empty canvas for most of a home miner's day (one block an hour on a small card). The legend has three items for one kind of mark. | blocks card |
|
||||
| Activity shows raw engine lines: "consensus parameters from the signed manifest: {"difficulty_v2_activation_daa":33000, ..." as a JSON blob, "remote job: update check now; a newer version installs at once". The feed is a developer log, not a user's activity. | Activity |
|
||||
| The rig shot: the 4070 row says "restart in 4 s · 12 GB · 6783 blocks · ne…" and the hash reads `n/a` in ember; the second sentence is cut. The reason for the restart (the engine's `message`) is not on the row. | 15 |
|
||||
| "A power cap is not applied yet: Settings has a Retry for the administrator prompt." A problem on the Mine page sends the user to another page to fix it. Two taps minimum. | the note under the rows |
|
||||
| The top bar repeats the pill: "paused · node synced · up 24 min" and then a pill saying PAUSED. | top bar |
|
||||
| "1 OF 1 ON" eyebrow in capitals is a count of switches, not of cards mining. | card head |
|
||||
|
||||
Apple would: one hero (the big button and the live total: MH/s, W, £ a day), the card rows as the only list, a
|
||||
one-line "today" (blocks, £, kWh), and move the blocks strip and the activity feed behind one "Activity" view.
|
||||
|
||||
### 2.6 Prove (`05-prove-live.png`), 5/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| 95 words of explanation before the switch: "short mathematical proof, in pieces called shards. The chain assigns shards to your keys ... a full shard needs 20.4 GB of GPU memory, measured". A home miner wants one sentence: can my card do this, and do I earn from it. | lead card |
|
||||
| Two paragraphs say "Off" twice ("Off. Switch it on and this machine proves..."). | lead card |
|
||||
| Four tiles with 0: state Off, assigned 0, proven 0, paid 0 "0.0000 IGN earned". Three zeros with no reason word. The reason is on the first tile ("off") and nowhere near the others. | the strip |
|
||||
| "Verifier: off: relay only" in ember, then 50 words on what a verifier is, then "external node: the app did not start it, so it set no verifier". Internal plumbing. | Verifier card |
|
||||
| "Program · pinned guest", "shard program id", "aggregator id": two 66-character hashes with Copy. Nobody at home copies these. | Program card |
|
||||
| "Proving on; the first shard arrives within a minute" toast, "needs setup, one-time install, about 20 minutes" row: the only user-facing decision (install the prover) is a small button in the lead card. | pv-setup |
|
||||
|
||||
Apple would: one card. A tier sentence per card ("RTX 5090 proves while it mines", "RTX 4070 (12 GB) cannot prove: a
|
||||
proof needs 20.4 GB", "Apple M5 Max proves on the CPU, slowly"), the switch, and one earnings line ("0.0000 IGN from 0
|
||||
proofs"). The ids and the verifier go behind "Details".
|
||||
|
||||
### 2.7 Rewards (`06-rewards-live.png`), 6/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "Balance: --, shown in the wallet, not here yet". A placeholder on the money page. The engine has no balance field (miner-ui-2 follow-up, never built). | balance tile |
|
||||
| "This run: 0 since the app started, 24 min a…" cut off. | tile |
|
||||
| No money. Blocks found 23,056 with no value. No reward per block, no IGN total, no £. | whole page |
|
||||
| "Save your key" is a card with ember "ONCE" every time the page opens, even after the key is saved and acknowledged. | key card |
|
||||
| "Use another address" is an open text box with Change next to it, on the same screen as the key. Pasting a wrong address and tapping Change moves every future block with no confirmation. | address card |
|
||||
| The dev-fee line sits under the address box, in the "Use another address" card, where it has nothing to do. | r-devfee-line |
|
||||
| "Open the wallet page" is a ghost button at the bottom of the second card. The wallet is where the balance is. | r-wallet |
|
||||
|
||||
Apple would: one card, money first (IGN earned, blocks found, £ a day at today's rate when a price exists), the
|
||||
address with Copy, then "Change address" and "Show my key" as two rows that confirm in place.
|
||||
|
||||
### 2.8 Node (`07-node-live.png`, syncing `18-node-syncing.png`, clock `21-node-clock.png`), 5/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| Four tiles, four cards, eleven numbers: headers, DAA score, difficulty, tips, blue score, digest, next switch, last lock, age, votes sent, peers. A home miner needs one line: "the node is synced, 6 peers". | whole page |
|
||||
| "DAA score", "blue score", "tips", "difficulty 137.73M", "consensus digest not printed yet", "finality miner-only", "last lock none yet", "age n/a", "votes sent 0 · waiting for the miner": every one is jargon or a 0 without a reason (the finality 0 has one, in a note, in grey). | Chain, Consensus, Finality |
|
||||
| "Synced" appears three times (the tile, the Sync card, the top bar). | tiles, Sync card |
|
||||
| The Sync card repeats the node tile's sentence word for word. | Sync card |
|
||||
| The clock block (21) is right in shape (a card with Sync clock and the manual hint) but sits on the Node page; a home miner whose clock is wrong is looking at Mine, where the hash is 0. The rule here says the strip carries the clock only on the setup screens. | n-clock |
|
||||
| "Read 5 s ago", "read every 10 s": two staleness marks for one reading. | tile, Chain eyebrow |
|
||||
|
||||
Apple would: one line on Mine ("Node synced · 6 peers · 109,859 blocks") and a Details view for the rest, since the
|
||||
digest and the switches are needed when something is wrong, not every day.
|
||||
|
||||
### 2.9 Updates (`08-updates-live.png`, job strips `13`, `14`), 6/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "Consensus upgrade at height 198,000." under the version with no meaning (the height is behind the node already: 198,490). | the lead line |
|
||||
| The remote-jobs card: 60 words of explanation, a Check now, a 20-row table with columns job / kind / started / exit / report, job ids like `update-now-0313-switch-d937c69d`, "done (0)", "uploaded". This is the fleet operator's console, shown to every user. | Remote jobs |
|
||||
| "signing key sha256:8f186e37b48…" at the foot. | s-jobs-key |
|
||||
| Two "Check now" buttons on one page, one per card, both the same words. | both cards |
|
||||
| The switch "Install updates by itself" and the paragraph under it are right. | lead card |
|
||||
|
||||
Apple would: one card: the version, "up to date, checked 13 min ago", the switch, Check. The jobs go into a history
|
||||
row ("Igneum ran 20 jobs on this machine, the last 16:36") that opens the table.
|
||||
|
||||
### 2.10 Settings (`09-settings-live.png`, rig `16-settings-nvidia-rig.png`), 4/10
|
||||
|
||||
The page with the most work in it and the hardest to use.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| The per-card block mixes a slider, two status lines, a tune line with a button, and an identities stepper with 30 words of help, per card. On the rig shot each card is 370 px tall; three cards fill two screens. | set-card |
|
||||
| "Power cap 80% · 460 W", "cap applied: 460 W", "draw 441 W GPU 71 °C memory 92 °C efficiency n/a": the same watts three times; efficiency `n/a` while draw and rate are both known (the engine's `eff_mhw` was 0 in the mock; live it is computed). | set-card lines |
|
||||
| "tuning: not run yet (starts after 120 s of steady mining)" with a Tune now button; nothing says when the next scheduled tune is, or when the last one ran. | tune line |
|
||||
| Ember Tune's switch has a 118-word paragraph under it: the power ladder, the clock ladder, 75 s a step, 1% tolerance, rejected hashes, hot GPUs, dragged memory clocks, fleet priors, privacy. This is the engineering spec, on a switch. | s-sweep help |
|
||||
| Power control: three sentences, then the same sentence again from the state ("Off: NVIDIA cards measure only; AMD cards need no rights." twice in the live shot). | s-power-control help |
|
||||
| "Identities: Each identity votes and is paid on its own. 8 suits a big card, 2 a small one, 1 an integrated GPU. Changing it restarts that card's worker." A stepper 1 to 64 for a concept the user cannot picture. The engine picks a default per card already. | ids |
|
||||
| "cap not applied (needs the administrator prompt) Retry" in ember: this is the one thing a Windows NVIDIA user must do, and it is a tiny button inside the second card's fourth line. | 16, the 4070 |
|
||||
| "name: a name for this machine · Rename. A label for you only. Keys come from the machine id d937c69d, never from the name." Jargon for a text box. | This machine |
|
||||
| "Vote on finality checkpoints: Your miner signs a checkpoint every 30 s. Votes are what lock the chain; leave it on." A switch whose help says never to touch it. | s-vote |
|
||||
| "Trust proof records without verifying them" under Advanced: devnet only, restarts the node. | s-trust |
|
||||
| "Dev fee 1% (1 block in 100)" with a switch: right, but the fee's own line is "Dev fee 1% (1 block in 100); the miner names the address when it starts". | s-devfee |
|
||||
| Logs: two buttons and two file paths. Right, but the log is also in the rail. | Logs card |
|
||||
| No electricity price anywhere, so nothing in the app can say £. | whole page |
|
||||
|
||||
Apple would: Settings as a list of plain rows: Tuning (goal, Power control, schedule), Start at login, Updates,
|
||||
Remote jobs, Dev fee, Electricity price, This machine's name, Logs, Advanced. The per-card controls live on the
|
||||
card, on Mine, where the numbers are.
|
||||
|
||||
### 2.11 The update card (`11-update-card-ready.png`), 9/10
|
||||
|
||||
The best screen in the app: the mark with a ring, the name, one line, three notes, the size, two buttons. It says
|
||||
"Igneum Ember 0.3.7" while the app is "Igneum Miner" everywhere else (`UpdateCard.NAME`). Keep it, fix the name.
|
||||
|
||||
### 2.12 The notices strip (`12-strip-update-downloading.png`, `13-strip-job-running.png`, `14-strip-job-failed.png`), 7/10
|
||||
|
||||
One notice at a time, actions on the right, a close. Right. Two problems: the job strip shows raw `RESULT shard=2
|
||||
prove_s=39.8` lines as the detail (engine output to a user), and the strip pushes the whole page down 100 px when it
|
||||
appears (the hero row jumps).
|
||||
|
||||
### 2.13 The log drawer (`10-logs-drawer-live.png`), 8/10
|
||||
|
||||
Chips, search, a time jump, last error, last swap, wrap, copy, follow, a ruler. Complete and quick (virtualised).
|
||||
It is a developer's tool and it is fine as one. "WATCH" as a source chip means nothing to a user; the lines under it
|
||||
are the observer's. Keep, rename nothing, hide behind Logs as now.
|
||||
|
||||
### 2.14 The tray and the menu bar item
|
||||
|
||||
macOS (`app/mac/IgneumMiner.swift`): Open Igneum Miner, Pause mining / Resume mining, "Node: synced, 109,859 blocks,
|
||||
6 peers", "Blocks found: 23,056", Quit Igneum Miner. Windows (`app/windows/host.cpp`): the tip "Igneum Miner: 17.0
|
||||
MH/s, 8,058 blocks found, node synced", a menu with Open, Pause and Quit, and a balloon "The window is in the tray.
|
||||
Right-click the icon to pause or quit." Both fine. Neither shows money or watts; both get them when the state has
|
||||
them (the Rust side formats the tip from `hash_total`, `accepted_total`, `node.state`; a `watts_total` and a
|
||||
`pounds_per_day` field would complete it; owed, not built here).
|
||||
|
||||
### 2.15 Narrow (`22-narrow-900-mine.png`, `23`, phone `24-phone-mine.png`, `25`)
|
||||
|
||||
900 x 600: the rail folds to icons, the hero goes two by two, the rows drop the power column. Usable. 390 wide: the
|
||||
rail still takes 150 px, "Stop mining" is cut to "Stop minin", the GPU row's numbers overflow the card to the right
|
||||
(`413` half off screen), the tiles truncate every sub-line. The Windows window can be dragged to 400 px wide and the
|
||||
layout breaks there. No phone layout exists.
|
||||
|
||||
### 2.16 Light mode (`26-light-scheme-mine.png`)
|
||||
|
||||
There is none. `<meta name="color-scheme" content="dark">`, one token set on `:root`, no `prefers-color-scheme`
|
||||
block. The page is dark whatever the OS says.
|
||||
|
||||
## 3. Every feature the old UI has (the build's tick list)
|
||||
|
||||
| # | Feature | Where it is today | Route |
|
||||
|---|---|---|---|
|
||||
| 1 | Welcome, GPU pick, address choice, key sheet | the three setup screens | `api/detect`, `api/cards`, `api/setup`, `api/key/saved`, `api/start` |
|
||||
| 2 | Start / stop mining | Mine big button | `api/pause`, `api/resume` |
|
||||
| 3 | Per-card on/off switch | Mine row, setup row | `api/cards` (the whole list) |
|
||||
| 4 | Hash rate, temperature, power per card; totals | Mine row, hero tiles | state |
|
||||
| 5 | Blocks found, this run, last 10 min, last hour, dev fee count, chain height | Mine tiles and blocks card | state |
|
||||
| 6 | Next program countdown and epoch | Mine tile | `program` |
|
||||
| 7 | Blocks strip (10 min canvas) | Mine | state `found` |
|
||||
| 8 | Activity feed | Mine | `events` |
|
||||
| 9 | Hot-plug states: new card, removed, not usable (Code 43) | Mine row, strip | state |
|
||||
| 10 | Proving switch, setup button, state/assigned/proven/paid, segments line, verifier, program ids | Prove | `api/prove`, `api/prove/setup` |
|
||||
| 11 | Rewards address with Copy, source, blocks, this run, balance placeholder | Rewards | state |
|
||||
| 12 | Show / hide the private key, the wallet file path | Rewards | `api/key/reveal` |
|
||||
| 13 | Change the rewards address | Rewards | `api/settings {address}` |
|
||||
| 14 | Open the wallet page | Rewards | `api/open` |
|
||||
| 15 | Node state, height, peers, version, chain numbers, digest with Copy, switches list, next switch, finality, sync ETA | Node | state |
|
||||
| 16 | Clock skew card with Sync clock and the manual hint | Node, setup strip | `api/clock/sync` |
|
||||
| 17 | Version, update note, Check now, Install now, auto-update switch | Updates | `api/update/check`, `install`, `auto`, `open` |
|
||||
| 18 | The update card (modal) and the strip notices | everywhere | same |
|
||||
| 19 | Remote jobs: allow switch, Check now, note, history table, signing key | Updates, Settings | `api/jobs/allow`, `api/jobs/check` |
|
||||
| 20 | Per-card power cap slider (NVIDIA), applied / not applied + Retry, draw and temperatures, efficiency | Settings card | `api/cards {power_pct}`, `api/power/apply` |
|
||||
| 21 | Per-card identities stepper | Settings card, setup row | `api/cards {identities}` |
|
||||
| 22 | Ember Tune switch, per-card Tune now / Stop / Unpin, the tune line | Settings | `api/sweep/enable`, `start`, `stop`, `pin` |
|
||||
| 23 | Power control switch | Settings | `api/power/control` |
|
||||
| 24 | Start at login, vote, dev fee, machine name, trust mode, live page | Settings | `api/settings` |
|
||||
| 25 | Copy the log, show the log, the log and chain folders | Settings | `api/log` |
|
||||
| 26 | The log drawer (chips, search, time, last error, last swap, wrap, copy, follow, ruler, resize) | rail | `api/log` |
|
||||
| 27 | Quit (with a confirm dialog) | rail | `api/quit` |
|
||||
| 28 | The pill, the top status, the rail foot (name, address, version, chain) | chrome | state |
|
||||
| 29 | Keyboard: arrow keys on the rail, Escape on the card, Enter on the time box | chrome | none |
|
||||
| 30 | `?page=`, `?screen=`, `?update=`, `?job=`, `?logs=1`, `?visible=1`, `?debug=1`, `?host=mac` | URL | none |
|
||||
|
||||
Ember's new fields (ember-tune e9738dd, from the agent): `tune_step`, `tune_steps`, `tune_eta_s`, `tune_plan`
|
||||
(full | confirm | baseline | climb), `tune_curve`, `tune_mem_mhz`, `mem_cap_mhz`, card states `tuning` and `held`;
|
||||
`settings.tune_goal` (efficiency | balanced | rate), `settings.power_price_pence`, `settings.tune_climb`,
|
||||
`settings.tune_period_s`; `POST /api/tune/goal {goal, price_pence, climb}`. No next-tune field: next = `sweep_at +
|
||||
tune_period_s`, "due now" when `sweep_at` is 0.
|
||||
|
||||
## 4. The top ten findings
|
||||
|
||||
| # | Finding | Consequence per tier |
|
||||
|---|---|---|
|
||||
| 1 | No money anywhere. No electricity price, no £ a day, no IGN earned on Mine or Rewards. | Every tier. A home miner with one card cannot tell whether the app pays for its power; a rig owner cannot compare cards. |
|
||||
| 2 | Three red `n/a` per card row on Apple silicon, 0.0 MH/s in ember, "off" on a row whose switch is on. A paused Mac looks broken. | macOS, every card. Apple silicon never reports W or °C, so every Mac user sees this forever. |
|
||||
| 3 | Tuning is split: the schedule and the switch on Settings, the row on Mine, the tune line per card on Settings, no "tuned 2 h ago, next Sunday", no Tune all, no goal. | Every tier with a tunable card (NVIDIA, AMD on Windows). |
|
||||
| 4 | Settings per-card block is 370 px per card: slider, identities, tune line, telemetry. A six-card rig scrolls through 2,200 px of controls. | Rigs most; single cards get a page that looks like a rig's. |
|
||||
| 5 | Prove is 95 words plus four zeros plus two hashes. The tier sentence ("this card cannot prove: 12 GB") is not said anywhere. | 8, 12 and 16 GB cards see a switch that will fail; 24 and 32 GB cards do not see that they can. |
|
||||
| 6 | Node is eleven numbers for one fact. DAA, blue score, tips, digest, lock, votes: all jargon with no reason words. | Every tier; nothing to do per tier. |
|
||||
| 7 | The remote-jobs history table and the signing key are on every user's Updates page. | Every tier sees the fleet console. |
|
||||
| 8 | Fixes live on another page: "cap not applied, Settings has a Retry", "Switch a GPU on below first", "Settings turns it off". Two taps for every fix. | Windows NVIDIA most (the administrator prompt). |
|
||||
| 9 | No light mode; no layout under 900 px (the Windows window can be narrower; the rail eats 150 px at 390). | Windows laptop users with a half-screen window. |
|
||||
| 10 | Developer lines leak into user surfaces: the Activity feed's JSON, the job strip's `RESULT` lines, "Igneum Ember" on the update card, "worker Metal", "pinned guest", "miner-only", "shown in the wallet, not here yet". | Every tier. |
|
||||
|
||||
Smaller, fixed in the build with no mention elsewhere: the key sheet says "Settings can show it again" (it is
|
||||
Rewards); "Start mining" on the address screen opens the key sheet instead; "Synced" three times on Node; two
|
||||
"Check now" on Updates; "Once" eyebrow on the key card forever; the retired ringed coin on Welcome; "Mined by GPUs.
|
||||
Proven by fire." (aphorism); the quit `confirm()` dialog (the viewer never shows dialogs: the rule in the brief).
|
||||
|
||||
## 5. What the previous redesign (miner-ui-2) set out to do and what it achieved
|
||||
|
||||
It set out to split one page into sections, put every setting on a section with one line of help, and lay out from
|
||||
900 x 600. It achieved all three: the rail works, the keyboard works, nothing was lost, the update card and the
|
||||
strip are good. What it did not do, by its own section 4: the balance, the pause-while-I-use-it switch, the log
|
||||
export. What it did not see: it moved the controls off the cards and on to a page, so the thing the user tunes (a
|
||||
card) and the controls that tune it are two sections apart; it kept the engine's vocabulary on every page; and it
|
||||
never put a currency on screen. UI 3 keeps the rail's sections as views, puts the card back at the centre, and
|
||||
adds the money.
|
||||
277
docs/plans/miner-ui-3.md
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
# Igneum Miner UI 3: the card is the product
|
||||
|
||||
6 October 2026. the project lead, 18:00 UTC: "as gorgeous as possible and as simple as possible but not lacking any features,
|
||||
think of this as an Apple product in terms of UI and UX, with tuning easy, buttons to tune each card again, or if
|
||||
automated then fair enough." The audit is `docs/plans/miner-ui-3-audit.md`. Branch `miner-ui-3`, worktree
|
||||
`../igneum-wt-miner-ui-3` from master b79f2cb. The 0.3.14 cut takes it. Ember Tune's engine (branch `ember-tune`,
|
||||
agent a855dcc4bd05e0615) is not touched: this plan reads its fields and posts to its routes.
|
||||
|
||||
## 1. The one job of each screen
|
||||
|
||||
| Screen | Its one job | What it holds | What left it |
|
||||
|---|---|---|---|
|
||||
| Welcome, GPU, address, key | get to mining in two taps with an address the user owns | as miner-ui-2, shorter words, the mark instead of the coin, the key sheet alone | the aphorism, "seed", "secp256k1", "worker", the devnet vote-key note |
|
||||
| Mine | is the machine mining, and what is it worth | the big button, the live total (MH/s, W, £ a day), the card rows, the tuning card, the node line, activity | the next-program tile (to the node details), the four bare counts, the JSON in the feed |
|
||||
| Earnings | what did it make, where does it go | money and IGN first, blocks, the address, change address, show key, the dev fee, the wallet | the balance placeholder, the "once" eyebrow |
|
||||
| Prove | can this machine prove, and is it | one switch, one tier sentence per card, one counts sentence, Set up when needed; details behind a disclosure | the 95-word lead, the four zero tiles, the two hashes on the surface |
|
||||
| Settings | the switches, one sentence each | tuning (goal, Ember Tune, Power control, electricity price), this machine, updates (one card), remote jobs (one row, history behind it), logs, appearance, advanced | the per-card controls (to the card), the spec paragraph, the second "Off" sentence |
|
||||
| Node | gone as a section | one line on Mine; "Details" opens the chain numbers, digest, switches, finality, version, clock | the eleven-number page |
|
||||
| Updates | gone as a section | one card on Settings; the update card and the strip as they are | the jobs table on the surface |
|
||||
| Logs | the engineer's view | the drawer as it is | nothing |
|
||||
|
||||
Four sections in the rail: Mine, Earnings, Prove, Settings. Logs and Quit in the foot. The rail keeps the arrow
|
||||
keys and `aria-current`.
|
||||
|
||||
## 2. The card row, the hero object
|
||||
|
||||
One row per card, ordered by performance (branch `card-order` ffb2bfa, `View.shownCards`). Everything a card can
|
||||
say is on its row; everything a card can be told is on its row or one tap under it.
|
||||
|
||||
```
|
||||
[NV] NVIDIA GeForce RTX 5090 DISCRETE 122 MH/s 290 W £1.95 a day 61 °C [Tune] (o)
|
||||
● mining · 32 GB · 3 blocks 0.42 MH/W at 28p/kWh
|
||||
Tuned 2 h ago · 2470 MHz at 100% · next check Sunday ⌄
|
||||
```
|
||||
|
||||
| Part | Rule |
|
||||
|---|---|
|
||||
| Name and model | `cd.name`, the kind pill (Discrete, Integrated, External, Apple silicon), the tooltip as today (`View.cardTitle`) |
|
||||
| State word | always present, with the reason: `mining`, `tuning: step k of n` (plus "about N min left"), `paused`, `waiting for the node`, `restarting in N s` (plus the engine's message), `starting`, `off` (plus the reason when the engine gives one), `held for a remote job: <title>`, `stopped after repeated faults`, `failed`, `not usable (Code 43)` (plus the reboot hint), `removed` |
|
||||
| Rate | `hash_now` as `122 MH/s` (one decimal under 100); blank with the state word when not mining; never a 0 |
|
||||
| Power | `power_w` as `290 W` with `0.42 MH/W` under it (`eff_mhw`, or rate over watts); a card that reports no draw (Apple silicon, an integrated card) hides the cell and the sub-line says `no power or temperature reading on Apple silicon` once |
|
||||
| Money | `£1.95 a day` = W × 24 / 1000 × price / 100 at the electricity price; with no price the cell reads `set a price` and taps to the price field; with no draw the cell is hidden |
|
||||
| Temperature | `61 °C`, amber from 86 °C (memory 90), ember from 92 °C (memory 95); hidden when not reported |
|
||||
| Switch | the card on or off, as today (`api/cards` with the whole list) |
|
||||
| Tune | `Tune` queues `api/sweep/start {key}`; during a run the button is `Stop` (`api/sweep/stop`); hidden for a card with no control and no measurement (`sweep_supported` false and vendor other) |
|
||||
| Tune line | the schedule sentence, section 4 |
|
||||
| Disclosure | the chevron opens the card's details: the power cap slider (NVIDIA, with the watt number, the applied or Retry line), identities (stepper, one sentence), pin or unpin, the memory temperature, the clocks, the device facts, the last tune's table (`tune_curve`) when there is one |
|
||||
|
||||
The rule from ember-tune.md 6a holds everywhere: a card never shows 0 MH/s without a reason word. A paused miner's
|
||||
row says `paused`; the switch stays on because the card is on. The old `n/a` cells are gone: a cell is shown with a
|
||||
number or not shown.
|
||||
|
||||
## 3. The Mine page
|
||||
|
||||
```
|
||||
[ Start mining ] 122 MH/s · 290 W · £1.95 a day · 3 blocks today
|
||||
mining on 1 of 1 card
|
||||
|
||||
Your cards [Tune all]
|
||||
row
|
||||
row
|
||||
|
||||
Tuning Efficiency | Balanced | Maximum about £1.95 a day at this goal
|
||||
Ember Tune on: once after install, then every 7 days. Last tune 2 h ago, next check Sunday.
|
||||
Power control [switch] Lets the app set NVIDIA power and clock limits. Windows asks once.
|
||||
|
||||
Node synced · 6 peers · 109,859 blocks · read 5 s ago Details ⌄
|
||||
|
||||
Activity Open the log
|
||||
strip (today's blocks on a 10-minute line)
|
||||
the last 8 events, one line each
|
||||
```
|
||||
|
||||
| Element | Rule |
|
||||
|---|---|
|
||||
| The big button | the only primary (ember) control on the page. Start mining / Stop mining / Tuning (disabled, "mining again in about N min") / Held / Stopping. `View.toggle`, with ember-tune's tuning and held states. |
|
||||
| The live total | `hash_total` MH/s; W = the sum of `power_w` over present cards; £ a day from that sum; blocks today = `found` in the last 24 h is not on the state, so "N this run" (`accepted_session`) with the lifetime count in the sub-line |
|
||||
| Tune all | queues `api/sweep/start` for every present, enabled, tunable card in list order (the engine runs one at a time). Hidden when Ember Tune is off fleet-wide (`settings.tuning_off`), where the line says why. |
|
||||
| Goal | a three-way segmented control, `settings.tune_goal` efficiency / balanced / rate (labelled Maximum), `POST /api/tune/goal {goal}`. The consequence line: at Efficiency the tuned watts (`sweep_watts` summed) at the price; at Maximum the default watts (`power_default_w` summed); Balanced between. A build without the route (0.3.13) gets a toast and the control stays on its old value. |
|
||||
| Schedule | `sweep_at` and `settings.tune_period_s` (default 604,800): "Last tune 2 h ago, next check Sunday" per the last-tuned card; "Not tuned yet: the first tune starts 2 min into steady mining" when nothing has run; "Tuning now: RTX 5090, step 7 of 9" while one runs. |
|
||||
| Power control | one switch, one sentence. Shown on Mine only when an NVIDIA card is present and control is off (the fix where the problem is); always on Settings. |
|
||||
| Node line | `View.nodeWords`; Details opens the old Node page's content in place: the chain numbers (headers, DAA score, difficulty, tips, blue score, each with its one-line meaning), the digest with Copy, the switches with the next one, finality, the version, the clock card when the clock is off (the clock card also shows above the big button when it blocks mining). |
|
||||
| Activity | the blocks canvas at 110 px with "N blocks in the last 10 min · N in the last hour"; the feed shows the newest 8 events, a line cut at 140 characters, a JSON tail replaced by "(details in the log)"; a link opens the drawer |
|
||||
|
||||
## 4. Tuning in words
|
||||
|
||||
| State | The row's tune line | Where else |
|
||||
|---|---|---|
|
||||
| never run, Ember Tune on | `Not tuned yet: starts after 2 min of steady mining` | the Tuning card's schedule line |
|
||||
| running | `Tuning: step 7 of 9 · about 4 min left` with a thin progress bar (`tune_step / tune_steps`) and the Stop button; the state word reads `tuning: step 7 of 9` and the rate stays live | the strip (ember-tune's `tuneNotice`), the big button |
|
||||
| tuned | `Tuned 2 h ago · 2470 MHz at 100% · next check Sunday`, the Tuned line's numbers (`tune_line`: 122.3 MH/s at 290 W, 0.422 MH/W) in the details | the Tuning card |
|
||||
| from the fleet prior | `Tuned 2 min ago from the fleet prior, confirmed · next check Sunday` | |
|
||||
| measure only (Apple, NVIDIA with Power control off, AMD on Linux) | `Measured 122 MH/s at 290 W (0.42 MH/W) as it runs` and the reason from `sweep_note` (`measure only on Apple silicon: the system sets the clocks and the power`) | Power control switch on Mine for the NVIDIA case |
|
||||
| pinned | `Your cap stays pinned at 80% · measured ...` with Unpin in the details | |
|
||||
| stopped | `Tuning stopped: a remote job took the GPU` until the next run | |
|
||||
| fleet pause | `Tuning paused fleet-wide by the signed manifest` on the Tuning card; the Tune buttons hidden | Settings |
|
||||
| no control, no measurement (an integrated card, vendor other) | no tune line, no button | |
|
||||
|
||||
Next check = `sweep_at + settings.tune_period_s`, as the weekday when under 7 days away, else the date; "due now"
|
||||
when `sweep_at` is 0 and the card mines. The engine also re-tunes on a driver or program change; the line does not
|
||||
predict that.
|
||||
|
||||
## 5. Earnings
|
||||
|
||||
```
|
||||
£0.00 earned IGN: nothing is bought or sold on devnet
|
||||
0.0000 IGN from 0 proofs
|
||||
23,056 blocks lifetime · 0 this run · 1 in 100 to the dev fee (0 so far) [dev fee switch]
|
||||
£1.95 a day electricity at 28p/kWh for 290 W Set the price
|
||||
|
||||
Rewards address 0xdd44…86E8 [Copy] made on this machine
|
||||
Change address ⌄ → a box, Change, then in place: "Pay 0x12…34 from the next block? Confirm Cancel"
|
||||
Show my key ⌄ → in place: "Show the private key on screen? Anyone who sees it can spend." Show Cancel
|
||||
then the key box, Copy, Hide, the file path
|
||||
[Open the wallet] the balance is in the wallet
|
||||
```
|
||||
|
||||
Money first, IGN second, as asked. Devnet has no price for IGN, so the £ earned line says why it is £0.00 instead
|
||||
of showing a placeholder; the electricity line is real money and is shown as a cost. When a market price exists the
|
||||
engine adds `price_gbp_per_ign` and the line fills in (owed, engine).
|
||||
|
||||
## 6. Prove
|
||||
|
||||
```
|
||||
Prove on this machine [switch]
|
||||
RTX 5090: proves while it mines (32 GB; a full shard needs 20.4 GB).
|
||||
RTX 4070: cannot prove a full shard (12 GB; a full shard needs 20.4 GB).
|
||||
Apple M5 Max: proves on the CPU, slowly.
|
||||
State: idle · nothing assigned · 0 assigned · 0 proven · 0 paid · 0.0000 IGN
|
||||
[Set up] About 20 minutes, once. (only when the prover is not installed)
|
||||
Details ⌄ verifier (word and reason), the program id and the aggregator id with Copy, the segments line
|
||||
```
|
||||
|
||||
The tier sentence per card: NVIDIA with 24 GB or more "proves while it mines"; NVIDIA under 24 GB "cannot prove a
|
||||
full shard (N GB; a full shard needs 20.4 GB)"; Apple silicon "proves on the CPU, slowly"; AMD and other "cannot
|
||||
prove yet (the prover is CUDA only)". The 20.4 GB and the 24 GB rule are the app's own words today
|
||||
(`index.html`, Prove lead); the engine's `proving.available` and `backend` win when they disagree.
|
||||
|
||||
## 7. Settings
|
||||
|
||||
Plain rows, one sentence each, grouped:
|
||||
|
||||
| Group | Rows |
|
||||
|---|---|
|
||||
| Tuning | goal (segmented) · Ember Tune (switch: "Tunes every card for hashes per watt: once after install, then every 7 days, and after a driver or program change.") · Power control (switch: "Lets the app set NVIDIA power and clock limits. Windows asks for administrator rights once. Off, NVIDIA cards are measured only.") · Electricity price (pence per kWh; the £ figures use it) · Hill climb (switch, Ember 2, when `tune_climb` is on the state) |
|
||||
| This machine | Start at login · Name · Allow remote jobs from Igneum ("Signed jobs run here once and report back.") with "History" opening the table and the signing key · Appearance: System / Light / Dark |
|
||||
| Updates | one card: "Igneum Miner 0.3.13 · up to date, checked 13 min ago" · Check · Install now when ready · "Install updates by itself" switch |
|
||||
| Logs | Copy the log · Show the log · the two folders |
|
||||
| Advanced | Vote on finality checkpoints · Trust proof records (devnet, confirm in place, restarts the node) · Open the live devnet page · the machine id |
|
||||
|
||||
The dev fee moved to Earnings (it is money). Identities and the power cap moved to the card.
|
||||
|
||||
## 8. Confirmations in place
|
||||
|
||||
No `confirm()`, no dialogs (the viewer never shows them). A destructive or costly action turns its own row into a
|
||||
question with two buttons:
|
||||
|
||||
| Action | The row says |
|
||||
|---|---|
|
||||
| Quit | `Quit Igneum Miner? Mining stops first, then the node. Quit Cancel` |
|
||||
| Change address | `Pay 0x12…34 from the next block? The miner restarts. Confirm Cancel` |
|
||||
| Show my key | `Show the private key on screen? Anyone who sees it can spend what the address holds. Show Cancel` |
|
||||
| Trust mode | `Include unverified proof records? Devnet only; the node restarts. Turn on Cancel` |
|
||||
| Stop a tune, stop mining, switch a card off, Tune all, the dev fee, identities | no question: each reverts in one tap and the toast says what happened |
|
||||
|
||||
## 9. Type, spacing, colour
|
||||
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| `--t-xs` 11, `--t-sm` 12, `--t-base` 13, `--t-md` 14, `--t-lg` 15, `--t-xl` 17, `--t-num` 24, `--t-hero` 34, `--t-h2` 28, `--t-h1` 44 | px | eight sizes; Unbounded only for the page title, the big button, the hero numbers and the row's rate; IBM Plex Sans for words; IBM Plex Mono for labels, units and small numbers |
|
||||
| `--s-1` 4, `--s-2` 8, `--s-3` 12, `--s-4` 16, `--s-5` 24, `--s-6` 32 | px | one spacing scale; cards pad `--s-5`, rows pad `--s-4`, gaps `--s-3` |
|
||||
| radius | 16 card, 12 row, 10 button, 999 pill | |
|
||||
| dark | obsidian `#0C0C0E` page, graphite `#16161A` card, `#111114` row, lines `#2A2A30` | the brand |
|
||||
| light | `#F4F1EC` (bone) page, `#FFFFFF` card, `#FAF8F5` row, lines `#E2DED8`, ink `#16161A`, ash `#6B6B70` | `prefers-color-scheme: light` and `[data-theme=light]`; ember stays `#F2541B`, molten darkens to `#B8731F` for text on light |
|
||||
| ember | the big button, the one primary per view, the rate, errors | one accent |
|
||||
| molten | live states (mining, synced), the pill | |
|
||||
|
||||
Contrast: every text on its background at 4.5:1 or more (ash on obsidian 5.9:1; ash on bone 5.2:1).
|
||||
|
||||
## 10. Phone width and the narrow window
|
||||
|
||||
| Width | Layout |
|
||||
|---|---|
|
||||
| 1180 px and up | rail 196 px, the hero in one row, the rows with every cell |
|
||||
| 900 to 1180 | rail folds to icons, the hero wraps to two lines |
|
||||
| 720 to 900 | rail 76 px, the row's money and temperature cells go under the name as a second line |
|
||||
| under 720 | the rail becomes a bottom tab bar (Mine, Earnings, Prove, Settings; Logs and Quit under Settings), the hero stacks, every row is two lines (name and state; numbers in a 2 by 2 grid), the big button full width, 16 px gutters, no horizontal scroll |
|
||||
|
||||
## 11. What the engine is asked for (none of it built here)
|
||||
|
||||
| Field or route | Why | Owner |
|
||||
|---|---|---|
|
||||
| `settings.tune_goal`, `settings.power_price_pence`, `settings.tune_period_s`, `POST /api/tune/goal` | the goal, the price, the schedule | ember-tune (exists at e9738dd) |
|
||||
| `tune_step`, `tune_steps`, `tune_eta_s`, `tune_plan`, `tune_curve`, states `tuning` and `held` | the running line, the details table | ember-tune (exists) |
|
||||
| `watts_total` on `mining` and `pounds_per_day` | the tray tip and the menu bar; the UI sums the cards itself | engine, owed |
|
||||
| `price_gbp_per_ign` | £ earned when a market exists | engine, later |
|
||||
| `address.balance_wei` | the balance on Earnings (miner-ui-2 follow-up) | engine, owed |
|
||||
|
||||
Until a field arrives the UI treats it as unset: no goal control change is sent to a build without the route (the
|
||||
toast says "needs the Ember Tune update"), the price is kept in the page's own storage as a fallback, the schedule
|
||||
line says "not tuned yet".
|
||||
|
||||
## 12. Tests
|
||||
|
||||
`view.test.mjs` grows with: the row model for every state word (mining, tuning with step and eta, paused, waiting,
|
||||
restarting with the message, off with the reason, held, faulted, failed, unusable, removed), the money line at a
|
||||
price and without one, the power and temperature cells hidden when not reported, the tune line for never, running,
|
||||
tuned, prior, measured, pinned, stopped and fleet-paused, the next-check weekday, the prove tier sentence per vendor
|
||||
and memory, the node line, the goal consequence, the quit and address confirmations' words. `tune-line.test.mjs`
|
||||
is ember-tune's version (the row word, the button, the strip). `notices.test.mjs` and `update-card.test.mjs` are
|
||||
unchanged and pass.
|
||||
|
||||
## 13. The build (6 October 2026, evening)
|
||||
|
||||
Branch `miner-ui-3`, three UI files rewritten, nothing on the Rust side (the engine is read as it is; the Ember
|
||||
fields are read when present and treated as unset when a build lacks them). Screenshots of the result beside the
|
||||
audit's: `docs/plans/miner-ui-3/n00-*.png` to `n26-*.png` (the mock on port 4317 from this worktree, the rig,
|
||||
the Mac, tuning, tuned with the details open, the node details, earnings with the address ask, prove, settings,
|
||||
the quit ask, paused, syncing, the clock, the update card, the job strip, the logs, 900 px, 390 px, light).
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `app/igneum-app/ui/index.html` | four pages behind the rail; the Mine hero (big button, the live total: MH/s, W, £ a day, blocks); the Tuning card (goal, schedule, Power control when it is the fix); the node line with Details; Activity with the strip and eight events; Earnings (money rows, the dev-fee switch, the address, Change address and Show my key as disclosures with their asks in place); Prove (switch, tier list, one counts line, Set up, Details); Settings (Tuning, This machine with the jobs row and History, Appearance, the update card, Logs, Advanced with the trust ask); the quit ask strip; the key sheet with the key first; the welcome with the mark, not the retired coin |
|
||||
| `app/igneum-app/ui/app.css` | dark and light token sets (`prefers-color-scheme` and `[data-theme]`), the row grid (`.gl-main`, `.nums`, `.acts`, `.gl-tune`, `.gl-details`), the totals, the segmented control, the disclosures, the asks, the bottom tab bar under 720 px, the folded rail under 1000 px |
|
||||
| `app/igneum-app/ui/app.js` | `Notices` with ember-tune's `tuneNotice`; `UpdateCard.NAME` is "Igneum Miner" (was "Igneum Ember"); `View` adds `rowWord`, `cells`, `poundsPerDay`, `money`, `effOf`, `wattsTotal`, `noReading`, `tuneWords`, `nextCheck`, `schedule`, `goalWords`, `nodeLine`, `proveTier`, `proveLine`, `eventLine`, `addressAsk`, `trustAsk` and ember-tune's `tuneWord`, `tuneEta`, `tuningCard` and toggle states; `TuneLine` is ember-tune's plus `pounds`; the DOM block renders the four pages, the row with its details (cap slider, identities, pin, telemetry, the tune table, the facts), the asks, the goal (`POST api/tune/goal`, a toast when the build lacks it), the price (the engine's `power_price_pence` when set, else the page's own storage), the appearance; `?tune=running|tuned|measured` for screenshots |
|
||||
| `app/igneum-app/ui/view.test.mjs` | 7 new tests: the row word per state (paused on an on card, held, tuning, starting, failed), money and the cells, the tune line for every state, the next check, the schedule and the goal, the prove tier and line, the node line, the activity line, the asks |
|
||||
| `app/igneum-app/ui/tune-line.test.mjs` | ember-tune's version (e9738dd) as is, so the ember-tune merge carries no test change |
|
||||
| `app/igneum-app/ui/update-card.test.mjs` | one expectation: the card's name |
|
||||
|
||||
`node --test` on the four UI files: 35 pass. The Rust side is untouched, so the app's unit tests are as master's; the
|
||||
0.3.14 shipper runs them on PC 2 as usual (the rule: test suites run on the PCs).
|
||||
|
||||
### The tick list (section 3 of the audit)
|
||||
|
||||
| # | Feature | Where it is now |
|
||||
|---|---|---|
|
||||
| 1 | Setup screens and the key sheet | as before; the address screen's button says Continue until "Use my own address" is picked |
|
||||
| 2 | Start / stop | the big button |
|
||||
| 3 | Per-card switch | the row |
|
||||
| 4 | Rate, temperature, power per card; totals | the row's cells (left out when not reported); the hero's total with W and £ |
|
||||
| 5 | Blocks found, this run, last 10 min, last hour, dev-fee count, chain height | the hero (this run, lifetime), Activity's line, the dev fee on Earnings, the height on the node line |
|
||||
| 6 | Next program countdown and epoch | the node details |
|
||||
| 7 | Blocks strip | Activity, 110 px |
|
||||
| 8 | Activity feed | Activity, eight lines, JSON cut |
|
||||
| 9 | Hot-plug states | the row (the words and the strip as before) |
|
||||
| 10 | Proving switch, setup, counts, segments, verifier, program ids | Prove: the switch, the tier list, one line, Set up, Details |
|
||||
| 11 | Address with Copy, source, blocks | Earnings |
|
||||
| 12 | Show / hide the key, the file path | Earnings, "Show my key" with the ask |
|
||||
| 13 | Change the address | Earnings, "Change address" with the ask |
|
||||
| 14 | Open the wallet | Earnings |
|
||||
| 15 | Node state, height, peers, version, chain numbers, digest, switches, finality, sync ETA | the node line and its Details |
|
||||
| 16 | Clock card with Sync clock | Mine, above the hero (and the strip on the setup screens) |
|
||||
| 17 | Version, update note, Check, Install now, auto-update | Settings, one card |
|
||||
| 18 | The update card and the strip | unchanged |
|
||||
| 19 | Remote jobs: allow, Check now, note, history, signing key | Settings, This machine: the switch, the line, History |
|
||||
| 20 | Power cap slider, applied / Retry, telemetry, efficiency | the row's details; MH/W on the row |
|
||||
| 21 | Identities | the row's details |
|
||||
| 22 | Ember Tune switch, Tune / Stop / Unpin, the tune line | Settings (switch); the row (Tune, Stop, the line); Unpin in the details; Tune all on the card list |
|
||||
| 23 | Power control | Settings, and Mine's Tuning card when an NVIDIA card has it off |
|
||||
| 24 | Start at login, vote, dev fee, name, trust, live page | Settings (vote and trust under Advanced), the dev fee on Earnings |
|
||||
| 25 | Copy the log, show the log, the folders | Settings, Logs |
|
||||
| 26 | The log drawer | unchanged |
|
||||
| 27 | Quit | the rail, with the ask strip instead of a dialog |
|
||||
| 28 | The pill, the rail foot | the pill (with "tuning"); the foot has the name, the address, the uptime |
|
||||
| 29 | Keyboard | the rail's arrows (left and right too), Escape on the card and the quit ask, Enter on the price and the time box |
|
||||
| 30 | The URL switches | as before, plus `?tune=` |
|
||||
| new | Goal, Tune all, the price, £ a day, MH/W on the row, the tune schedule, the prove tier, the node line, light mode, appearance, the bottom tab bar | this build |
|
||||
|
||||
### Owed
|
||||
|
||||
| What | Who |
|
||||
|---|---|
|
||||
| `watts_total` and `pounds_per_day` on the state for the tray tip and the menu bar; `address.balance_wei`; `price_gbp_per_ign` | the engine (section 11) |
|
||||
| The wallet's copy of the update card still says "Igneum Ember" (`app/igneum-wallet/ui/update-card.js`) | the wallet |
|
||||
| A Windows and a PC screenshot of the row with a real NVIDIA card (the telemetry cells, the cap slider, Retry) | the 0.3.14 cut, first thing to look at |
|
||||
| The row's state word while tuning carries the meta after it ("tuning: step 7 of 9 · measuring 441 W · 32 GB · ...") and is cut on a narrow row | a later pass: drop the meta while tuning |
|
||||
| The merge with `ember-tune`: `app.js` takes this branch's file (it already carries ember-tune's UI words and test); the engine files merge by themselves | the 0.3.14 shipper |
|
||||
BIN
docs/plans/miner-ui-3/00-welcome.png
Normal file
|
After Width: | Height: | Size: 206 KiB |