diff --git a/.github/workflows/ci-red.yml b/.github/workflows/ci-red.yml new file mode 100644 index 00000000..48b3deff --- /dev/null +++ b/.github/workflows/ci-red.yml @@ -0,0 +1,45 @@ +# The red watcher as its own workflow, on workflow_run, so the copy on master watches EVERY branch's ci run whatever +# ci.yml that branch carries: GitHub runs a workflow_run workflow from the default branch only, and the branch's own +# ci.yml never enters it (7 October 2026: the inline `red` job of ci.yml was conditioned on master and release-*, and +# a feature branch would have waited for a merge of master before its reds were posted at all). +# +# One line per failed run (tools/ci/red-watch.mjs record, idempotent per run attempt) to /srv/ci-red/red.jsonl on the +# box; the box's igneum-ci-red.timer posts each new line once to the hidden updates channel, naming the branch, the +# commit, the red check and the pushing author. Runs 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). Never blocks a +# release: it reads the run, writes one line, and ends. +name: ci-red +on: + workflow_run: + workflows: [ci] + types: [completed] +jobs: + red: + name: red watcher (every branch; one line per failed run, with the branch, commit, red check and pushing author, to the updates channel and the box file) + if: ${{ github.event.workflow_run.conclusion == 'failure' }} + # the label ci-red is on igneum-build-1 only (added through the runners API on 7 October 2026; the default of + # RUNNER_LABELS in provision.sh carries it): the record file and the poster (igneum-ci-red.timer, the webhook file) + # live on that box, and the pool label igneum-build-1 is shared with igneum-build-2 since the same day + runs-on: [self-hosted, linux, x64, ci-red] + timeout-minutes: 5 + permissions: + actions: read # the failed run's jobs API (the first real red run, 21:19Z on 6 October: 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 the failed run (one line, the branch, the commit, the failed jobs and their first failed step from the run's own API, the pushing author) + env: + GITHUB_TOKEN: ${{ github.token }} + RED_WATCH_RUN_ID: ${{ github.event.workflow_run.id }} + RED_WATCH_ATTEMPT: ${{ github.event.workflow_run.run_attempt }} + RED_WATCH_WORKFLOW: ${{ github.event.workflow_run.name }} + RED_WATCH_BRANCH: ${{ github.event.workflow_run.head_branch }} + RED_WATCH_SHA: ${{ github.event.workflow_run.head_sha }} + RED_WATCH_EVENT: ${{ github.event.workflow_run.event }} + RED_WATCH_URL: ${{ github.event.workflow_run.html_url }} + RED_WATCH_ACTOR: ${{ github.event.workflow_run.actor.login }} + RED_WATCH_TITLE: ${{ github.event.workflow_run.head_commit.message }} + RED_WATCH_AUTHOR: ${{ github.event.workflow_run.head_commit.author.name }} + run: node tools/ci/red-watch.mjs record --file /srv/ci-red/red.jsonl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f080596..fefdd8b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,12 +8,16 @@ # 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) +# Where it runs: `pow` and `sims` go to the self-hosted pool (label igneum-build-1: the runners on igneum-build-1 and, since +# 7 October 2026, igneum-build-2, which carries that label too; rustc pinned, sccache read-only) and only when the push +# touched code (the `changes` job; a docs-only push skips them) # 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. +# has no fallback in runs-on, the variable is the switch). The `site` job stays on GitHub's machines. The red watcher +# is its own workflow, .github/workflows/ci-red.yml (workflow_run, so the copy on master watches every branch's run +# whatever ci.yml that branch carries): one line per failed run, naming the branch, the commit, the red check and the +# pushing author, to the hidden updates channel and to /srv/ci-red/red.jsonl (tools/ci/red-watch.mjs; +# infra/build-server/ci-red), so nobody opens the Actions page to learn a branch is red (the inline `red` job here +# watched master and release-* only until 7 October 2026, when eight red runs on ca3-v4-node went unseen). # # 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 @@ -24,8 +28,39 @@ on: push: pull_request: jobs: + changes: + # What the push touched (tools/ci/docs-only-check.sh): a push of documents only (docs/, site/, *.md) skips the two + # compile-or-compute jobs below, which read none of those paths, so the self-hosted queue carries only runs that can + # change their result (7 October 2026: 31 runs queued on one runner, most of them status-document pushes). The tree + # gate (the `site` job) runs on ubuntu-latest for every push. A pull request, a new branch or a force push answers + # code=true (no `before` to compare from), as does any error reading the compare API: when in doubt, run. + name: what the push touched (docs-only runs skip the Rust and simulator jobs) + runs-on: ubuntu-latest + outputs: + code: ${{ steps.classify.outputs.code }} + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: tools/ci + - id: classify + env: + GH_TOKEN: ${{ github.token }} + BEFORE: ${{ github.event.before }} + AFTER: ${{ github.sha }} + REPO: ${{ github.repository }} + EVENT: ${{ github.event_name }} + run: | + if [ "$EVENT" != push ] || [ -z "$BEFORE" ] || [ "$BEFORE" = 0000000000000000000000000000000000000000 ]; then + echo "code=true" >> "$GITHUB_OUTPUT"; echo "no base to compare from ($EVENT): the compile jobs run"; exit 0 + fi + files="$(gh api "repos/$REPO/compare/$BEFORE...$AFTER" --paginate --jq '.files[].filename' 2>/dev/null || true)" + line="$(printf '%s\n' "$files" | bash tools/ci/docs-only-check.sh)" + echo "$line" >> "$GITHUB_OUTPUT" + echo "$line: $(printf '%s\n' "$files" | grep -c .) changed path(s) between ${BEFORE:0:8} and ${AFTER:0:8}" pow: name: igneum-pow tests, igneum-census build + needs: changes + if: ${{ needs.changes.outputs.code == 'true' }} runs-on: ${{ vars.IGNEUM_CI_RUNNER == 'box' && fromJSON('["self-hosted", "linux", "x64", "igneum-build-1"]') || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 @@ -41,6 +76,10 @@ jobs: run: cargo build --release sims: name: simulators, quick modes + needs: changes + # master and release-* pushes, and pull requests into them, only (main, 7 October 2026: every code push cost two box jobs and the + # queue read 22); a feature-branch code push runs the igneum-pow tests alone. tools/ci/sims-branch-check.sh holds this rule. + if: ${{ needs.changes.outputs.code == 'true' && ((github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))) || (github.event_name == 'pull_request' && (github.base_ref == 'master' || startsWith(github.base_ref, 'release-')))) }} runs-on: ${{ vars.IGNEUM_CI_RUNNER == 'box' && fromJSON('["self-hosted", "linux", "x64", "igneum-build-1"]') || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 @@ -70,32 +109,13 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '22' + - name: a headless Chromium for the text-overlap sweep (Playwright outside the tree; the gate finds it through IGNEUM_PLAYWRIGHT_DIR) + run: | + mkdir -p /tmp/pw && cd /tmp/pw && npm init -y >/dev/null && npm i --no-audit --no-fund playwright@1.56 | tail -1 + npx playwright install --with-deps chromium | tail -1 + echo "IGNEUM_PLAYWRIGHT_DIR=/tmp/pw" >> "$GITHUB_ENV" - 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 - - 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 diff --git a/brand/marks/vendor-marks.mjs b/brand/marks/vendor-marks.mjs new file mode 100644 index 00000000..08de8b84 --- /dev/null +++ b/brand/marks/vendor-marks.mjs @@ -0,0 +1,65 @@ +// Igneum vendor and OS marks (gpu-logos, 7 October 2026): the strings the miner app ships in app/igneum-app/ui/app.js +// (View.MARKS and View.VENDORS), exported verbatim for the site and anything else that names the hardware. +// view.test.mjs fails when this file and app.js drift apart, so edit app.js first and regenerate this file with +// `node brand/marks/regen.mjs` (or copy the strings by hand; the test says which one moved). +// +// Each glyph: a hand-drawn simplified monochrome mark of the vendor's public geometry (never a copied logo file, +// never a raster), 24 x 24 viewBox at 22 px, under 460 bytes, fill or stroke through currentColor so the element's +// colour tints it. Nominative use that names the hardware; the ember accent is for state and never tints a brand. +// +// The treatment in the app (app.css, the block at the end): a 44 x 44 well, radius 12, background the vendor colour +// at .14 alpha (dark) or .10 (light), a 1 px ring in the vendor colour at .45 alpha, the glyph in the full colour; +// hover and focus-within add a 3 px halo of the well colour; nothing animates. The light hex of every vendor reads at +// 3:1 or better on its well over white (nvidia 3.87, amd 5.01, intel 4.29, apple 7.52, gpu 4.65). +// +// Class names in the app: .badge. (nvidia | amd | intel | apple | gpu), .badge.mini for a 26 px inline mark, +// .gen for the series line under the name. Tokens: --mark-, --mark--well, --mark--ring. + +export const VENDORS = { + "nvidia": { + "label": "NVIDIA", + "dark": "#8BE37A", + "light": "#2F8A22" + }, + "amd": { + "label": "AMD Radeon", + "dark": "#FF5A5A", + "light": "#C41E2A" + }, + "intel": { + "label": "Intel", + "dark": "#7CC4FF", + "light": "#1C6FD6" + }, + "apple": { + "label": "Apple", + "dark": "#E6E3DD", + "light": "#4A4A50" + }, + "gpu": { + "label": "GPU", + "dark": "#9A9A9E", + "light": "#6B6B70" + } +}; +export const WELL_ALPHA = { dark: 0.14, light: 0.1 }; +export const MARKS = { + "nvidia": "", + "amd": "", + "intel": "", + "apple": "", + "gpu": "" +}; +// OS marks in the same treatment: Apple is the vendor glyph; Windows is the four slanted panes +export const OS_MARKS = { + macos: MARKS.apple, + windows: "" +}; +export const OS_COLOURS = { macos: VENDORS.apple, windows: { label: 'Windows', dark: '#7CC4FF', light: '#1C6FD6' } }; +// the CSS tokens for both themes, as the app declares them +export function tokensCss() { + const line = (theme) => Object.entries(VENDORS).map(([v, c]) => { const h = c[theme], r = parseInt(h.slice(1, 3), 16), g = parseInt(h.slice(3, 5), 16), b = parseInt(h.slice(5, 7), 16), a = String(WELL_ALPHA[theme]).replace(/^0/, ''); return `--mark-${v}:${h};--mark-${v}-well:rgba(${r},${g},${b},${a});--mark-${v}-ring:rgba(${r},${g},${b},.45)`; }).join(';'); + return `:root{${line('dark')}}\n@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){${line('light')}}}\n:root[data-theme="light"]{${line('light')}}`; +} +// the well:
+export function markHtml(vendor, size) { const v = MARKS[vendor] ? vendor : 'gpu'; return '
' + MARKS[v] + '
'; } diff --git a/docs/analysis/era-vdf-2026-10-07.md b/docs/analysis/era-vdf-2026-10-07.md new file mode 100644 index 00000000..691b63dd --- /dev/null +++ b/docs/analysis/era-vdf-2026-10-07.md @@ -0,0 +1,86 @@ +# The era VDF: built, measured and gated (7 October 2026) + +Era VDF lane, 7 October 2026, from the attack pass's F7 row (`docs/analysis/attack-pass/f7-era.md`, sub-row a: the node's era seed was a plain chain block hash, grindable with one block of hash at no delay, and the 1-hour VDF of spec 04 section 4.4 did not exist in the node). Repository branch `era-vdf` (this record, the spec text, the harness `tools/era-vdf/`, the fast-time fields); node fork branch `era-vdf-node` on the 0.3.19 line (`release-0.3.19-node` dc141409). Every number below names its log on igneum-build-1 under `/srv/builds/igneum-wt-era-vdf/ev-*/`. + +## 1. What was built + +| Piece | Where | What | +|---|---|---| +| The integer | `consensus/core/src/era_vdf/bigint.rs` | a fixed-width signed integer (40 limbs, 2,560 bits) on the stack: add, sub, mul, shifts, Knuth division with floor, truncated, exact and Euclidean remainders, the extended gcd and the partial extended gcd with Lehmer's word steps (chiavdf `xgcd_partial.c`), modpow, sqrt and the fourth root, Miller-Rabin with the first 30 primes as bases; every operation checked against `num-bigint` on 20,000 random operands of the class group's sizes, the known-failed shapes first | +| The class group | `era_vdf/classgroup.rs` | `proto-vdf/src/classgroup.rs` (3 October 2026) on the fixed-width integer: NUDUPL and NUCOMP ported line by line from chiavdf's `qfb_nudupl` and `qfb_nucomp`, the plain duplication and Cohen 5.4.7 kept as the oracles the tests hold them to on random forms at 256, 512 and 1,024 bits; serialization as sign byte plus fixed width, 258 bytes a form | +| Wesolowski | `era_vdf/wesolowski.rs` | eval with serialized checkpoints (at most 2^16, 17 MB), the 12-bit-digit block prover bucketed per residue class and parallel over them, the naive prover as the oracle, verify; T + 1, another y, another pi and another input refused | +| The hash chain | `era_vdf/hashchain.rs` | scheme 1: T sequential SHA-256 applications from a tagged start; verification by recomputation; one step short refused | +| The scheme byte and the seed | `era_vdf/mod.rs` | `vdf_scheme` 0 and 1, `EraVdfProof` and its wire form, `era_vdf_input` (the chain's BLAKE2b keyed `IgneumEraVdfInput` over `chain_id || n || the day's blue hashes`), `era_seed_of` = SHA-256 of the scheme byte, the input, T and y | +| The switch | `consensus/core/src/config/params.rs`, `igneum.rs` | `pow_era_blocks` and `pow_era_lead` as override fields (the constants everywhere; in the digest when they differ), `era_vdf_activation_daa` (never), `vdf_scheme` (0), `era_vdf_t` (the reference T); the three in the digest once the activation is set (the 0.3.15 rule); installed with the PoW schedule | +| The node side | `consensus/src/processes/era_vdf.rs`, `model/stores/era_vdf.rs` | the cut rule (the chain block below the cut, memoised and re-validated by reachability), the day-of-blues input (memoised per cut block), the evaluator thread started by the virtual processor a quarter of the lead past the cut, the record store (one row per era), the header processor's wait when a header arrives before the record, the template's `era_seed` None while evaluating, `submit` for a record from outside (verified against this chain's input) | +| The template and the miner | `PowEpochInfo`, `RpcPowEpochInfo`, `rpc.proto` fields 37 to 44, `igneum-miner` | the era schedule, the VDF's state, scheme, T and input in every template; the miner holds while the node reports no era seed ("era VDF: the node is still evaluating"); `igneum-miner vdf bench|eval|verify` with the node's own code | +| The harness | `tools/era-vdf/reroll.mjs` | the F7 re-roll harness against the REAL era cut (era 120 DAA, lead 20 on the merged fast-time file; ports 30100 and up, suffix 1010), `--vdf off` the stand-in, `--vdf on` the VDF at a fast T, the adversary running the node's evaluator over its candidate before publishing | + +## 2. The parameters + +Measured 7 October 2026 on igneum-build-2 (AMD EPYC 9454P, 96 threads, Ubuntu 24.04), one core under `/srv/builds/_bin/lease cores 31` at nice 10 while the box ran other lanes' suites (load 25 to 75), with the node's own code (`igneum-miner vdf bench`, logs `ev-vdf-bench3.log`, `ev-vdf-bench5.log` in this lane's scratch) and chiavdf 7e62ce14 built on the box against GMP 6.3.0 (`ev-chiavdf`). + +| Parameter | Value | Label | +|---|---|---| +| Group | class group, 1,024-bit prime discriminant `D = -HashPrime("igneum-era-discriminant" \|\| input)`, `\|D\| = 7 mod 8` | Implemented (spec 4.2) | +| Generator, Fiat-Shamir prime, proof plan | `(2, 1, (1 - D) / 8)`; 256 bits; 12-bit digits, at most 2^16 serialized checkpoints (17 MB) | Implemented | +| Proof on the wire | 529 bytes: scheme (1), T (8), two 258-byte forms with 2-byte lengths; `y` and `pi` 258 bytes each | Measured | +| Scheme byte | `vdf_scheme` 0 = class group, 1 = hash chain; genesis 0 everywhere | Implemented | +| Reference rate, scheme 0 | 30,589 and 40,117 squarings/s in two 10-s runs on the box core (the spread is the box's load); 30,000 is the reference | Measured | +| `T_era`, scheme 0 | 3,600 x 30,000 = 108,000,000 squarings (`ERA_VDF_T_CLASS_GROUP`): 60 min at the reference, 45 at the faster run | Measured, set | +| Prove, scheme 0 | eval + prove 11.6 s at T 401,167 (eval 10.0 s): the single-thread block prover is about 14 percent of the evaluation, parallel over residue classes in the node (up to 8) | Measured | +| Verify, scheme 0 | 21.9 and 22.6 ms with the group held (mean of 20); 184 ms with the discriminant derived, the derivation being 161 to 167 ms, once per era | Measured (section 5 for the gate) | +| Reference rate, scheme 1 | 16.2 and 17.0 million SHA-256/s (SHA-NI); 16,000,000 is the reference; `T_era` = 57,600,000,000 hashes (`ERA_VDF_T_HASH_CHAIN`) | Measured, set | +| Verify, scheme 1 | recomputation: 10.1 s for T 170 million, the full hour at `T_era` | Measured | +| Discriminant search | 161 to 167 ms per era (Miller-Rabin with the first 30 primes on the fixed-width integer) | Measured | +| chiavdf on the same core | 208.8 K squarings/s (`vdf_bench square`, NUDUPL over GMP, 1,000,000 iterations); the AVX-512 IFMA path (`square_asm`) gave 127.3 K at 20,000 iterations and stalled at 300,000 and above in this build (built outside its Makefile's `FAST_MACHINE` flags), so the IFMA number is not established here | Measured; the asm path unestablished | +| Delay on the fastest prover measured | 108,000,000 / 208,800 = 517 s against the 1-s block interval (517x) and the 2-s publish window (259x); a prover 10x chiavdf's GMP path (the ceiling Chia's and the EF's hardware efforts aimed at, approximate, from memory) would still take 52 s, 26x the window | Computed from the measurements | +| The gate "at least 60x one block interval on the fastest known prover" | 517x on chiavdf's GMP path, the fastest evaluator measured on this hardware; PASS as measured, with the IFMA path unestablished (above) and the 10x hardware ceiling still 52x | PASS (measured), caveat recorded | + +The node's own evaluator is 5.2 to 6.8x slower than chiavdf's GMP path on the same core. That ratio only moves the honest side: `T_era` is set from the node's rate, so an honest node finishes in the hour; the attacker's margin is the delay at the fastest prover, above. + +## 3. The gate: the re-roll harness with the VDF off and on + +`tools/era-vdf/reroll.mjs` on igneum-build-2 (the box under other lanes' suites, nice 10; logs and per-cut JSON under `/srv/builds/igneum-wt-era-vdf/ev-harness-out/reroll-vdf-{on,off}-5.json`), three nodes of the fork at this record's commit on the fast-time file with `skip_proof_of_work`, era 120 DAA and lead 20 (cuts at `S = 120 n - 20`, one every two minutes), ports 30100 and up, suffix 1010; two honest virtual miners share 1 block/s on nodes 0 and 1; the adversary on node 2 holds a block A built on the tip at `S - 1` and tries to make it the era's cut block. With the VDF on, the adversary runs the node's own evaluator (`igneum-miner vdf eval`, the network's T) over its candidate before publishing; T is set from a 3-s bench at the start so the delay is about 5 s on one core of this box, five times the block interval. + +| Run | Switch | Cuts | A accepted | A became the cut block | Known-draw re-rolls (the seed the adversary knew before publishing is the era's seed) | Adversary's evaluation | Nodes agree on the era seed | Gate | Harness | +|---|---|---|---|---|---|---|---|---|---| +| vdf-off-5 (the stand-in, 15:08 to 15:22 UTC) | `era_vdf_activation_daa` never | 6 (eras 2 to 7) | 6 of 6 | 6 of 6 | 6 of 6: the seed is `hash(A)` every time | none needed: the draw of hash(A) is known the instant A is built | 3 of 3 on every era | FAIL (the known-pass fires) | SOUND | +| vdf-on-5 (the era VDF, 14:54 to 15:08 UTC) | activation 0, scheme 0, T 189,650 (5 s on this core) | 6 (eras 2 to 7) | 6 of 6 | 0 of 6 | 0 of 6 | 5.38 to 6.64 s, during which the honest chain advanced 3 to 10 blocks; A arrived behind them and never became the cut block | 3 of 3 on every era, the record ready (state 3) at every era start | PASS (silent) | SOUND | + +What the two runs say. Under the stand-in a miner with one block of hash at the right second owns the era draw outright on this network: holding the block at `S - 1` and publishing it the moment the chain reaches `S - 1` makes it the cut block in every one of six cuts (the attack lane's epoch-cut run saw 1 of 6, with the honest block often landing first; here the adversary is faster to the second), and its draw is known the instant the block is built. Under the VDF the same adversary cannot know any candidate's draw before T steps have run; while it ran them the honest chain moved 3 to 10 blocks, so its block arrived behind the cut and the seed came from the delay over the day of blues ending at the honest cut block, the same on all three nodes. The second half of the written argument of F7 (a) holds in the node, not only on paper: the re-roll needs the draw inside the window, and the window is 1 s against a delay of 5 s here and 517 s at the fastest prover measured on the production T (section 2). + +The known-pass and the known-fail ran on the same binaries, file and ports, the VDF switch the only difference. A first VDF-off run with a lookup defect in the harness (the adversary's block not found, so the verdict read "held") was discarded once the node's own log showed the adversary's hash as the cut block in 5 of 5 cuts; the harness now reads A from that log line. Two runs that overlapped on the box through a stale node of an earlier run were discarded as well (their nodes disagreed because they were two networks); the two runs above ran alone. + +## 4. The cut rule and the certified checkpoint (for the finality lane) + +The node names `C_era(n)` as the last selected-chain block below the cut on the header's own chain (the block the stand-in used), which is the checkpoint block the lead rule names under the O-4.3 decision of 3 October 2026 (certified or not). Three facts decide it: + +1. Determinism. A header's validity must be a function of its own past. "The certificate carried by a block in the header's past, for the highest-index checkpoint with DAA score at most the cut" is such a function, but a certificate that lands after the era starts flips the reading between headers of one era (a header before the carrier reads the fallback, a header after it reads the certificate), so the certified binding needs a second rule: the carrier must sit at most half a lead above the cut (3,600 DAA s, the merge depth) and be a chain ancestor of the header; under that rule every honest header of the era reads the same certificate once the network merged the carrier, and a header on a chain that never merged it reads the fallback, consistently with its own past. The chain-block reading needs no second rule. +2. Liveness. A finality pause across the cut (a third of weight leaving in an hour is a 30-day pause under rule v3) leaves the certified binding without a checkpoint for the era; the chain-block reading always has one, which is the reason O-4.3 was decided the way it was for the epoch. +3. The defence. The grinding defence is the delay: no candidate's draw is knowable for T steps, whichever block is the cut. The binding moves which block a withholder would have to be the author of, not whether withholding pays; both readings leave the withholder with a coin flip it cannot see. + +The change, if the finality lane wants the certified binding: `EraVdfManager::cut_block` (one function; the input, the delay and the seed are unchanged), plus the second rule above and a test with a certificate carried late. + +## 5. The verify gate on a 2019-class core + +The gate was "the VDF verifies in under 10 ms on a 2019-class core". Measured: 21.9 and 22.6 ms with the group held on the box core (above), which the F6 row's calibration puts at about 1.19x on an i7-9700K (the O-1.14 run: 6.0 ms on the 2019 core against 5.06 ms on the box proxy), so about 26 ms on a 2019 core, labelled a proxy: no 2019 host was rented this lane (Vast rentals are a purchase; not made without the project lead's word). NOT MET, by 2.2x on the box and about 2.6x on the proxy. + +Where the time goes and what closes it: a verification is two 256-bit exponentiations, about 770 group operations at 28 µs each; the operation is NUDUPL on the fixed-width integer, whose cost is the extended gcd (Lehmer rounds on 8-limb numbers) and the reduction. Three rounds of this lane moved it from 345 µs (a Lehmer convention defect that fell back to plain division every round) to 28 µs (the convention, i64 word division, 34 limbs, the x86-64 128-by-64 division); the next 2.2x is chiavdf's Pulmark reducer (reduce only when `a` exceeds 8 limbs, O-4.6) and a limb-level NUDUPL that keeps the partial gcd's intermediates in words, or GMP through `rug` behind a feature on the x86-64 Linux and Windows builds (chiavdf's 208 K/s is 6x this evaluator, which would put the verification near 4 ms as the prototype measured), with the fixed-width path the fallback for wasm and macOS. Owed, not blocking: the verification runs once per era (180 days) on a node that imports a record rather than evaluating; every mining node evaluates and never verifies. + +## 6. Consequences per tier (the standing rule of 5 October 2026) + +| Tier | What the era VDF costs | What it means | +|---|---|---| +| A home miner, any card (8, 12, 16, 24 or 32 GB), any vendor, Windows, Linux or macOS | one CPU core for about 60 min once per 180 days at the reference rate (a 2019-class desktop core about 72 min by the F6 calibration), 17 MB of host RAM for the prover's checkpoints during it, 0 bytes on the card; the node starts it a quarter of the lead past the cut and holds the record from then | nothing changes on the card or in the hash rate; the 2-hour lead covers a core half the reference speed; a node that was off across the cut evaluates on arrival and its miner holds until the record lands (the miner says so every 10 s) | +| A rig (one node, several cards) | the same one core on the rig's host, once per era | nothing per card | +| A pool user | the pool's node evaluates; the member's miner takes `era_seed` from the template as today | nothing | +| A light client or a syncing node | verifies an imported record in 22 ms (26 ms on a 2019 core, proxy) plus the 165-ms discriminant derivation, once per era; under scheme 1 it recomputes the hour | the 10-ms gate is missed (section 5); operationally one verification per 180 days | +| The protocol | the era draw's input is unknowable for 517 s on the fastest prover measured, against a 1-s block interval: the stand-in's one-block grind is closed (section 3) | the freeze of the draw procedure and the C_era cut rule no longer waits on the VDF's existence; it waits on the two decisions of `ledger-decisions.md` | + +## 7. What is owed + +- The P2P relay of an era record to a syncing peer and the RPC import (spec 4.5, O-4.10), before era 1 of any network with the switch set. +- The external review of the class-group port (O-4.1): the port is a second implementation checked against the textbook algorithms and `num-bigint`, not a review. +- The attack pass's F7 status row (branch `attack-pass`, `docs/analysis/attack-pass-2026-10.md`) reads INCOMPLETE pending this lane; the line for it, from section 3: "F7 (a): the era VDF is in the node (fork `era-vdf-node`); the re-roll harness against the real era cut fires with it off (6 of 6 cuts, the seed the adversary's block) and is silent with it on (0 of 6 across six cuts, the adversary's 5-s evaluation against a 1-s block interval, three nodes agreeing on every era seed); PASS, the delay 517 s on the fastest prover measured at the production T." +- The decisions of `docs/plans/ledger-decisions.md` (the activation per network, the cut's binding). diff --git a/docs/bench-log.md b/docs/bench-log.md index af812f22..0c328e6d 100644 --- a/docs/bench-log.md +++ b/docs/bench-log.md @@ -2521,6 +2521,25 @@ same once; a pool user nothing; a fleet operator gets a node that keeps its only every peer (13,354 lines of work it did not need in seven minutes). Owed: the fleet agent's synced-node reading; a receiver-side limit on certificates per index per minute as a second belt once the seed is fixed; the formatter's reflow of `finality.rs` (taken out of the commit). +## 6 October 2026, Counter ASIC 3.0: the class v4 rehearsal + +A fleet of real GPU boxes ran a fleet-only chain from the devnet's genesis state and crossed a class v4 activation by miner signal, in the shape the live devnet will see. Numbers only; the per-box record is `docs/plans/counter-asic-3-gate/class-v4-20261006-rehearsal-PASS.json`. + +| Number | Value | +|---|---| +| Nodes on the chain | 16 (15 mining, 1 seed) plus 37 more miners joining after the flip; 1 box left on the old binary as the stale case | +| Object | 17 fields, epochs of 600 DAA, the signal window 600 DAA, the threshold 9,500 bps, the floor at DAA 2,400; one consensus digest on every node | +| First block | 18:41:10 UTC | +| The flip | DAA 1,202, 19:00:5x UTC, by miner signal at epoch 2: 10,000 bps over the window, 599 of 599 blue blocks, the identical line on all 52 signalling nodes | +| Program ids | one id per epoch on every box for epochs 2, 3 and 4, each equal to the CPU verifier's class v4 id for that seed and era and different from the class v3 id of the same seed; the pre-flip epoch's id is a class v3 id | +| Blocks rejected for proof of work | 0 on every node over the whole run; every miner's CPU re-check mismatched 0 | +| The old-binary box | refused at every connect by consensus digest (11 refusals, 22 mismatch lines), never a peer; given the full object it exits at parse | +| The floor | crossed at DAA 2,400 with v4 already in force; the chain mined through it at about 1.6 blocks/s; one chain, no fork at the 19:34:54 UTC sweep (heights 2,502 to 2,509, blue 2,432 to 2,440) | +| Verifier against the GPU on the first v4 pack | 1,024 of 1,024 nonces at target ff..ff found by the GPU worker and re-derived by the CPU verifier, one digest on both sides | +| 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 @@ -2633,3 +2652,32 @@ in the same shape and reports a box behind its wanted binary. | 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 | + +## 7 October 2026, the first 16 GB card: an RTX 5060 Ti in a Thunderbolt enclosure on PC 2 (branch bench-5060ti) + +Machine: PC 2 (`1ccfe586`, Windows 11), an ASUS Dual GeForce RTX 5060 Ti (16 GB GDDR7, Blackwell sm_120, PnP `PCI\VEN_10DE&DEV_2D04&SUBSYS_8A111043`) in a Razer Core X V2 Thunderbolt enclosure ("USB4 Router (2.0), Razer - Core X V2", bus `0B:00.0`), beside the RTX 5090 on its own supply; NVIDIA driver 610.47 (WDDM 32.0.16.1047, the 5090's driver, nothing installed for the new card); the installed app 0.3.19 and its own `igneum-worker-cuda.exe` (NVRTC 12.8). Jobs `fetch-5060ti-packs-20261007` (the kit: `tools/bench-5060ti/make-kit.sh`, the class v4 pack at sub-version 1 and the v3 control, sha256 `fd8393ed...`, 105,892 bytes) and `run-5060ti-bench-20261007-b` (`tools/bench-5060ti/pc2-5060ti-bench.ps1`, 14:44:19Z, ran 14:45:05 to 14:58:11Z, exit 0; the 5060 Ti alone through the runner's `--cards-off`, the 5090 mining throughout; run `-a` died in 1 s on an argument-binding fault in the nvidia-smi query and is void). PC 2 lost power twice that day, so the job WRITES NO POWER LIMIT: it reads `power.limit` against `power.default_limit` (180 W = 180 W, range 150 to 198 W) and the row says `limit_is_stock=yes`. Read back with `node tools/jobs.mjs run-5060ti-bench-20261007-b`. + +**Detection** (the app's first poll after the restart, run `win-1ccfe586-20261007-143611`, 14:36:16Z): `GPUs: NVIDIA GeForce RTX 5090 (CUDA); NVIDIA GeForce RTX 5060 Ti (CUDA); AMD Radeon(TM) Graphics (OpenCL, gfx1036)` and `cards: NVIDIA GeForce RTX 5090 [discrete, off] | NVIDIA GeForce RTX 5060 Ti [discrete, off] | ...`; the app started a miner on it by itself (`nvidia-1ccfe586-2`, `--device 1`, 8 identities, the default). The kind reads `discrete`, not `external`: the app does not know it is an eGPU. nvidia-smi in the job: index 1, 16,311 MiB, PCIe link gen 4 x4 current against gen 4 x16 maximum (the Thunderbolt link: a quarter of the slot's lanes), 43 C idle. The freeze lane's cause class for the 15:10 UK hang on the first boot with the card: not the card (no TDR, no Thunderbolt or PCIe link event; Kernel-Power 41 + 6008, no bugcheck, the power shape again). + +**G1 and the window** (the installed CUDA worker, `--bench --batch-log2 24 --block-warps 1`, the card alone, `CUDA_VISIBLE_DEVICES` on its UUID so every row names the device; nvidia-smi every 2 s on the card, the loaded samples at utilisation 90 percent and over): + +| Pack | Dispatches of 2^24 | Self-test | Fingerprint 2^24 at base 0 | MH/s | +|---|---|---|---|---| +| mx8-devnet-epoch0 (the class v3 control) | 5 | PASS | 90f794dd556f7a3b (= the control everywhere) | 30.895 | +| v4-devnet-epoch0 (class v4, sub-version 1, program id 1a4230699a6b9c60) | 5 | PASS | 867dbc45cfb36b4d (= Metal, Apple OpenCL, the RTX 5090) | 30.879 | +| v4-devnet-epoch0, the 10-minute window at the stock limit | 1,105 (602 s) | PASS | 867dbc45cfb36b4d | 30.882 | + +| Row | Value | +|---|---| +| NVIDIA RTX 5060 Ti 16 GB, class v4, CUDA (NVRTC), driver 610.47, PCIe 4.0 x4 through the enclosure | 30.9 MH/s over 10 minutes on the card alone | +| Watts at the stock limit (180 W default, unchanged) | 114.8 W mean, 115 W p50 over the window; 0.269 MH/W; SM 2,753 MHz, memory 13,801 MHz, 60 C maximum | +| The class v4 shadow against the control | 0.1 percent (the 5090 paid 0.2, the 9070 XT 3, the B580 0.1) | +| The efficient point | OWED to the app's Ember Tune: nothing set by the job; PC 2's Power Helper refused every request since the restart ("the helper did not run sequence 0 within 15 s", 14:39Z), so no ladder ran on either card | +| Prove beside the miner (16 GB tier) | BLOCKED, not measured: the shipped WSL2 host (sha `71bc2438...`) carries no `IGNEUM_CUDA_DEVICE` selector, so aimed at anything it proves on CUDA device 0 (the 5090) through the app's own socket `/tmp/sp1-cuda-0.sock`; the selector lives in the prover-floor host (`proof_system.rs`, branch prover-floor) and is the owed cut. The job's inventory: the floor server IS on PC 2 (`/opt/igneum-floor/bin/sp1-gpu-server`, 6.8.1 build `e911facb...`, 166,665,880 bytes) beside the stock one (`~/.sp1/bin`, `c2642ad1...`), WSL sees the card as CUDA device 1 | +| Card-picker entry (`site/yourcard.js`) | `['NVIDIA RTX 5060 Ti', 30.9]`, added; the public table row in `site/miner-bench.json` | + +Against the 5090 on the same PC (122 MH/s at 308 W, 0.396 MH/W): 25.3 percent of its hash at 37 percent of its draw, 68 percent of its hash per watt. The dependent-read ceiling was not probed (the memprobe step is not in this job); at 128 loads a hash 30.9 MH/s is 3.95 G dependent reads a second, between the 9070 XT (2.4 to 2.7 G) and the 5090 (16 to 18 G). + +Consequences per tier (the rule of 5 October 2026): a 5060 Ti owner (16 GB, Windows) mines at 30.9 MH/s and 115 W from the box with nothing to set: about 5,100 blocks a day at the 522 MH/s the devnet showed at 14:44Z (one every 17 s, approximate: the network rate moves), about a quarter of a 5090 owner's 20,200, for 2.76 kWh a day (£0.79 at 28.5 p against the 5090's £2.11); through a Thunderbolt enclosure the x4 link costs nothing measurable (the hash is bound by the card's own memory latency, not the link; the 5090's PCIe-slot rows are the comparison), so a laptop with a Thunderbolt 4 port and this enclosure is a 31 MH/s miner. The 8 GB 5060 Ti: the same hash is the expectation (the 1 GiB dataset fits), a line owed. Proving on the 16 GB tier: the fleet's 4060 Ti 16 GB row (9.0 GB peak beside the miner on the patched server) says this card would mine and prove with about 7 GB spare, approximate until the host with the device selector ships; today the app's prover default leaves it off ("a full shard needs a 24 GB card") and the measured read is owed to the prover-floor host cut. Linux and HiveOS take the same CUDA worker (owed a line). What the lane does next: the prover-floor host's selector into the shipped WSL2 bundle, then the prove-beside read on this card; the Power Helper fault on PC 2 to the Ember lane (no efficient point on any PC 2 card until it answers). + +Found on the way: inside a PowerShell `@( ... )` the comma binds before `+`, so `'--query-gpu=' + $f, '--format=csv'` is one argument (run a, void in 1 s; the query string is built first now); a bare string inside a function that also returns a value is swallowed into the caller's variable (the sampler line; `[Console]::Out.WriteLine` now); the app's `kind` for a Thunderbolt card reads `discrete` (a word for the Cards page to earn: `external`, which the state already names). diff --git a/docs/community/discord-hooks.md b/docs/community/discord-hooks.md index 2c0bbb14..bb2d3b9c 100644 --- a/docs/community/discord-hooks.md +++ b/docs/community/discord-hooks.md @@ -14,6 +14,7 @@ eight fields, UK time in the footer with UTC in brackets, never a mention, never | 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. @@ -35,6 +36,7 @@ The watcher's texts are fixed sentences in the script, each a stated rule, and a ``` 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 --windows-sha --mac --mac-sha --hive --hive-sha \ --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] @@ -65,7 +67,9 @@ parenthetical dropped, 160 characters at most; a line that trips the guard is dr ## Credentials and deployment `~/.config/igneum/discord`, mode 600, KEY=VALUE lines: `DISCORD_WEBHOOK_NUMBERS`, `DISCORD_WEBHOOK_ANNOUNCEMENTS`, -`DISCORD_WEBHOOK_INCIDENTS`. Never in the repository, never printed; `check` prints which keys are set. +`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 diff --git a/docs/community/discord/README.md b/docs/community/discord/README.md new file mode 100644 index 00000000..48b420a8 --- /dev/null +++ b/docs/community/discord/README.md @@ -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. diff --git a/docs/community/discord/assets/banner-overlay-960x540.png b/docs/community/discord/assets/banner-overlay-960x540.png new file mode 100644 index 00000000..6f1b1ab9 Binary files /dev/null and b/docs/community/discord/assets/banner-overlay-960x540.png differ diff --git a/docs/community/discord/assets/emoji-block.png b/docs/community/discord/assets/emoji-block.png new file mode 100644 index 00000000..1d04927b Binary files /dev/null and b/docs/community/discord/assets/emoji-block.png differ diff --git a/docs/community/discord/assets/emoji-flame.png b/docs/community/discord/assets/emoji-flame.png new file mode 100644 index 00000000..ae2e8913 Binary files /dev/null and b/docs/community/discord/assets/emoji-flame.png differ diff --git a/docs/community/discord/assets/emoji-gpu.png b/docs/community/discord/assets/emoji-gpu.png new file mode 100644 index 00000000..aa0927f9 Binary files /dev/null and b/docs/community/discord/assets/emoji-gpu.png differ diff --git a/docs/community/discord/assets/emoji-hash.png b/docs/community/discord/assets/emoji-hash.png new file mode 100644 index 00000000..40e0c411 Binary files /dev/null and b/docs/community/discord/assets/emoji-hash.png differ diff --git a/docs/community/discord/assets/emoji-igneum.png b/docs/community/discord/assets/emoji-igneum.png new file mode 100644 index 00000000..633461fd Binary files /dev/null and b/docs/community/discord/assets/emoji-igneum.png differ diff --git a/docs/community/discord/assets/emoji-ladder.png b/docs/community/discord/assets/emoji-ladder.png new file mode 100644 index 00000000..aa4d6add Binary files /dev/null and b/docs/community/discord/assets/emoji-ladder.png differ diff --git a/docs/community/discord/assets/emoji-letter.png b/docs/community/discord/assets/emoji-letter.png new file mode 100644 index 00000000..86f7d241 Binary files /dev/null and b/docs/community/discord/assets/emoji-letter.png differ diff --git a/docs/community/discord/assets/emoji-lock.png b/docs/community/discord/assets/emoji-lock.png new file mode 100644 index 00000000..198f4ec7 Binary files /dev/null and b/docs/community/discord/assets/emoji-lock.png differ diff --git a/docs/community/discord/assets/emoji-proven.png b/docs/community/discord/assets/emoji-proven.png new file mode 100644 index 00000000..e4a5c187 Binary files /dev/null and b/docs/community/discord/assets/emoji-proven.png differ diff --git a/docs/community/discord/assets/emoji-shard.png b/docs/community/discord/assets/emoji-shard.png new file mode 100644 index 00000000..87db67ce Binary files /dev/null and b/docs/community/discord/assets/emoji-shard.png differ diff --git a/docs/community/discord/assets/guide-banner-1920x480.png b/docs/community/discord/assets/guide-banner-1920x480.png new file mode 100644 index 00000000..868475e7 Binary files /dev/null and b/docs/community/discord/assets/guide-banner-1920x480.png differ diff --git a/docs/community/discord/assets/invite-background-1920x1080.png b/docs/community/discord/assets/invite-background-1920x1080.png new file mode 100644 index 00000000..5ccdaf90 Binary files /dev/null and b/docs/community/discord/assets/invite-background-1920x1080.png differ diff --git a/docs/community/discord/assets/role-bot.png b/docs/community/discord/assets/role-bot.png new file mode 100644 index 00000000..62c7e13c Binary files /dev/null and b/docs/community/discord/assets/role-bot.png differ diff --git a/docs/community/discord/assets/role-builder.png b/docs/community/discord/assets/role-builder.png new file mode 100644 index 00000000..7f70ed0e Binary files /dev/null and b/docs/community/discord/assets/role-builder.png differ diff --git a/docs/community/discord/assets/role-community.png b/docs/community/discord/assets/role-community.png new file mode 100644 index 00000000..edc8a3c1 Binary files /dev/null and b/docs/community/discord/assets/role-community.png differ diff --git a/docs/community/discord/assets/role-core.png b/docs/community/discord/assets/role-core.png new file mode 100644 index 00000000..ba42f961 Binary files /dev/null and b/docs/community/discord/assets/role-core.png differ diff --git a/docs/community/discord/assets/role-early-miner.png b/docs/community/discord/assets/role-early-miner.png new file mode 100644 index 00000000..5f74bf5d Binary files /dev/null and b/docs/community/discord/assets/role-early-miner.png differ diff --git a/docs/community/discord/assets/role-founder.png b/docs/community/discord/assets/role-founder.png new file mode 100644 index 00000000..d252da90 Binary files /dev/null and b/docs/community/discord/assets/role-founder.png differ diff --git a/docs/community/discord/assets/role-miner.png b/docs/community/discord/assets/role-miner.png new file mode 100644 index 00000000..90887c9a Binary files /dev/null and b/docs/community/discord/assets/role-miner.png differ diff --git a/docs/community/discord/assets/role-node-runner.png b/docs/community/discord/assets/role-node-runner.png new file mode 100644 index 00000000..6655a419 Binary files /dev/null and b/docs/community/discord/assets/role-node-runner.png differ diff --git a/docs/community/discord/assets/role-pool-operator.png b/docs/community/discord/assets/role-pool-operator.png new file mode 100644 index 00000000..02728ab7 Binary files /dev/null and b/docs/community/discord/assets/role-pool-operator.png differ diff --git a/docs/community/discord/assets/role-prover.png b/docs/community/discord/assets/role-prover.png new file mode 100644 index 00000000..04cc69b7 Binary files /dev/null and b/docs/community/discord/assets/role-prover.png differ diff --git a/docs/community/discord/assets/role-researcher.png b/docs/community/discord/assets/role-researcher.png new file mode 100644 index 00000000..1b384b60 Binary files /dev/null and b/docs/community/discord/assets/role-researcher.png differ diff --git a/docs/community/discord/assets/role-verified-miner.png b/docs/community/discord/assets/role-verified-miner.png new file mode 100644 index 00000000..022e03bb Binary files /dev/null and b/docs/community/discord/assets/role-verified-miner.png differ diff --git a/docs/community/discord/assets/server-banner-960x540.gif b/docs/community/discord/assets/server-banner-960x540.gif new file mode 100644 index 00000000..2dc92453 Binary files /dev/null and b/docs/community/discord/assets/server-banner-960x540.gif differ diff --git a/docs/community/discord/assets/server-banner-960x540.png b/docs/community/discord/assets/server-banner-960x540.png new file mode 100644 index 00000000..9caf2f0c Binary files /dev/null and b/docs/community/discord/assets/server-banner-960x540.png differ diff --git a/docs/community/discord/assets/sticker-ember.png b/docs/community/discord/assets/sticker-ember.png new file mode 100644 index 00000000..219a7502 Binary files /dev/null and b/docs/community/discord/assets/sticker-ember.png differ diff --git a/docs/community/discord/assets/sticker-gpu.png b/docs/community/discord/assets/sticker-gpu.png new file mode 100644 index 00000000..dfc52cb7 Binary files /dev/null and b/docs/community/discord/assets/sticker-gpu.png differ diff --git a/docs/community/discord/assets/sticker-proven.png b/docs/community/discord/assets/sticker-proven.png new file mode 100644 index 00000000..c480799d Binary files /dev/null and b/docs/community/discord/assets/sticker-proven.png differ diff --git a/docs/community/discord/make-assets.py b/docs/community/discord/make-assets.py new file mode 100644 index 00000000..1a8482dc --- /dev/null +++ b/docs/community/discord/make-assets.py @@ -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)) diff --git a/docs/community/discord/make-banner-gif.sh b/docs/community/discord/make-banner-gif.sh new file mode 100755 index 00000000..c166b3fa --- /dev/null +++ b/docs/community/discord/make-banner-gif.sh @@ -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" diff --git a/docs/community/discord/structure.md b/docs/community/discord/structure.md new file mode 100644 index 00000000..52766e0b --- /dev/null +++ b/docs/community/discord/structure.md @@ -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, ": "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/" / "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 ` 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) | diff --git a/docs/design/genesis-forward-harness/digest-base-dc141409.log b/docs/design/genesis-forward-harness/digest-base-dc141409.log new file mode 100644 index 00000000..d49fb243 --- /dev/null +++ b/docs/design/genesis-forward-harness/digest-base-dc141409.log @@ -0,0 +1,29 @@ +Latency ladder inactive (no activation in the override file): every class v4 program at rung 0, 27 shadow passes; no ladder bits in the header +Program class signal inactive (the window or the floor is absent from the override file): headers carry block version 2 exactly +Fees on igneum-devnet-973: pgas table v0, B_p 30000000 pgas, S_p 7500000 pgas, floors 1000000000 wei per gas and 1000000000 wei per pgas; calibrated v1 from DAA score never +Consensus params digest: 079d8a7e736abbd4b135eb1d652466ca7906ea1b200e22eac97f795e626356e7 (exchanged in the p2p handshake; a peer with another digest is refused) +2026-10-07 15:14:29.331+02:00 [INFO ] igneumd/2.1.0-dc141409 +2026-10-07 15:14:29.331+02:00 [INFO ] Application directory: gf-digest/d-base +2026-10-07 15:14:29.331+02:00 [INFO ] Data directory: gf-digest/d-base/igneum-devnet-973/datadir +2026-10-07 15:14:29.331+02:00 [INFO ] Logs to console only +2026-10-07 15:14:29.350+02:00 [INFO ] Finality v2 (igneum-devnet-973): interval 30 depth 20 window 7200 DAA dust 5 presence 20 aggregators 8 ban 7200 fold 3; rule v3 (frozen table, certificate fold) from checkpoint DAA never; 0 checkpoints known, next index 1, 0 locks, 0 keys; C1 in DAA seconds from DAA never; weight-gated deep fork choice from DAA never; leave rule (W7, delay 3600 DAA) from checkpoint DAA never, 0 leaves +2026-10-07 15:14:29.363+02:00 [INFO ] [igneum-exec] proving v0: payouts from DAA score never, window 7200 DAA, dust 5, verifier Off +2026-10-07 15:14:29.363+02:00 [INFO ] [igneum-exec] proving v1: segment records from DAA score never, 8 blocks a segment, unproven after 600 DAA, aggregator share 1000 bps, shard program id unknown, aggregator id unknown +2026-10-07 15:14:29.364+02:00 [INFO ] [igneum-exec] verifying keys embedded: shard program id 0x2b1a81cb413236cf063077b46ed3111628f6c41036bcf6e23ee4cbbf5679ef7a aggregator id 0x474678f35f7545db28055d5e5bbc308231d84a5a072202087a2a8d5b09123896 +2026-10-07 15:14:29.370+02:00 [INFO ] template prewarm: on (the cached block template is rebuilt on every virtual change; IGNEUM_TEMPLATE_PREWARM=0 turns it off) +2026-10-07 15:14:29.371+02:00 [INFO ] GRPC Server starting on: 127.0.0.1:29770 +2026-10-07 15:14:29.372+02:00 [INFO ] P2P Server starting on: 127.0.0.1:29771 +2026-10-07 15:14:29.373+02:00 [INFO ] [igneum-exec] chain follower started +2026-10-07 15:14:29.373+02:00 [INFO ] [igneum-exec] proof verifier: Off (no SP1 verification on this node) +2026-10-07 15:14:29.373+02:00 [INFO ] [igneum-exec] exec sync: sink edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block 0; pruning point edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block Some(0) (DAA 0); retention root edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block Some(0) (DAA 0), its body held +2026-10-07 15:14:29.373+02:00 [INFO ] [igneum-exec] eth_ JSON-RPC listening on 127.0.0.1:26790 +2026-10-07 15:14:29.373+02:00 [INFO ] [igneum-exec] exec sync: no snapshot to resume from (no snapshot file); the follower starts at genesis +2026-10-07 15:14:29.373+02:00 [INFO ] [igneum-exec] exec sync: waiting for consensus to sync before the executor starts (the sink is 393269 s old) +2026-10-07 15:14:29.373+02:00 [WARN ] [igneum-exec] cannot bind the eth_ JSON-RPC server on 127.0.0.1:26790: Address already in use (os error 98) +2026-10-07 15:14:29.374+02:00 [INFO ] WRPC Server starting on: 127.0.0.1:29772 +2026-10-07 15:14:29.474+02:00 [INFO ] [igneum-exec] chain follower stopped +^SIGTERM - shutting down... +2026-10-07 15:14:54.316+02:00 [INFO ] P2P Server stopped: 127.0.0.1:29771 +2026-10-07 15:14:54.316+02:00 [INFO ] WRPC Server stopped on: 127.0.0.1:29772 +2026-10-07 15:14:54.317+02:00 [INFO ] GRPC Server stopped on: 127.0.0.1:29770 +2026-10-07 15:14:54.824+02:00 [INFO ] igneumd has stopped... diff --git a/docs/design/genesis-forward-harness/digest-no-file.log b/docs/design/genesis-forward-harness/digest-no-file.log new file mode 100644 index 00000000..e53e161c --- /dev/null +++ b/docs/design/genesis-forward-harness/digest-no-file.log @@ -0,0 +1,29 @@ +Latency ladder inactive (no activation in the override file): every class v4 program at rung 0, 27 shadow passes; no ladder bits in the header +Program class signal inactive (the window or the floor is absent from the override file): headers carry block version 2 exactly +Fees on igneum-devnet-973: pgas table v0, B_p 30000000 pgas, S_p 7500000 pgas, floors 1000000000 wei per gas and 1000000000 wei per pgas; calibrated v1 from DAA score never +Consensus params digest: 079d8a7e736abbd4b135eb1d652466ca7906ea1b200e22eac97f795e626356e7 (exchanged in the p2p handshake; a peer with another digest is refused) +2026-10-07 15:12:35.092+02:00 [INFO ] igneumd/2.1.0-75810130 +2026-10-07 15:12:35.093+02:00 [INFO ] Application directory: gf-digest/d-none +2026-10-07 15:12:35.093+02:00 [INFO ] Data directory: gf-digest/d-none/igneum-devnet-973/datadir +2026-10-07 15:12:35.093+02:00 [INFO ] Logs to console only +2026-10-07 15:12:35.128+02:00 [INFO ] Finality v2 (igneum-devnet-973): interval 30 depth 20 window 7200 DAA dust 5 presence 20 aggregators 8 ban 7200 fold 3; rule v3 (frozen table, certificate fold) from checkpoint DAA never; 0 checkpoints known, next index 1, 0 locks, 0 keys; C1 in DAA seconds from DAA never; weight-gated deep fork choice from DAA never; leave rule (W7, delay 3600 DAA) from checkpoint DAA never, 0 leaves; key succession (W5) from checkpoint DAA never, 0 successions; signature scheme 0 (0 = BLS12-381), any other refused until a class names it +2026-10-07 15:12:35.136+02:00 [INFO ] [igneum-exec] proving v0: payouts from DAA score never, window 7200 DAA, dust 5, verifier Off +2026-10-07 15:12:35.136+02:00 [INFO ] [igneum-exec] proving v1: segment records from DAA score never, 8 blocks a segment, unproven after 600 DAA, aggregator share 1000 bps, shard program id unknown, aggregator id unknown +2026-10-07 15:12:35.136+02:00 [INFO ] [igneum-exec] proof archive at gf-digest/d-none/igneum-devnet-973/datadir/evm/proofs: 0 proofs +2026-10-07 15:12:35.136+02:00 [INFO ] [igneum-exec] verifying keys embedded: shard program id 0x2b1a81cb413236cf063077b46ed3111628f6c41036bcf6e23ee4cbbf5679ef7a aggregator id 0x474678f35f7545db28055d5e5bbc308231d84a5a072202087a2a8d5b09123896 +2026-10-07 15:12:35.141+02:00 [INFO ] template prewarm: on (the cached block template is rebuilt on every virtual change; IGNEUM_TEMPLATE_PREWARM=0 turns it off) +2026-10-07 15:12:35.141+02:00 [INFO ] GRPC Server starting on: 127.0.0.1:29760 +2026-10-07 15:12:35.141+02:00 [INFO ] P2P Server starting on: 127.0.0.1:29761 +2026-10-07 15:12:35.141+02:00 [INFO ] [igneum-exec] eth_ JSON-RPC listening on 127.0.0.1:26790 +2026-10-07 15:12:35.141+02:00 [INFO ] [igneum-exec] proof verifier: Off (no SP1 verification on this node) +2026-10-07 15:12:35.142+02:00 [INFO ] [igneum-exec] chain follower started +2026-10-07 15:12:35.142+02:00 [INFO ] WRPC Server starting on: 127.0.0.1:29762 +2026-10-07 15:12:35.142+02:00 [WARN ] [igneum-exec] cannot bind the eth_ JSON-RPC server on 127.0.0.1:26790: Address already in use (os error 98) +2026-10-07 15:12:35.142+02:00 [INFO ] [igneum-exec] exec sync: sink edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block 0; pruning point edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block Some(0) (DAA 0); retention root edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block Some(0) (DAA 0), its body held +2026-10-07 15:12:35.142+02:00 [INFO ] [igneum-exec] exec sync: no snapshot to resume from (no snapshot file); the follower starts at genesis +2026-10-07 15:12:35.142+02:00 [INFO ] [igneum-exec] chain follower stopped +^SIGTERM - shutting down... +2026-10-07 15:13:00.089+02:00 [INFO ] P2P Server stopped: 127.0.0.1:29761 +2026-10-07 15:13:00.089+02:00 [INFO ] GRPC Server stopped on: 127.0.0.1:29760 +2026-10-07 15:13:00.089+02:00 [INFO ] WRPC Server stopped on: 127.0.0.1:29762 +2026-10-07 15:13:00.597+02:00 [INFO ] igneumd has stopped... diff --git a/docs/design/genesis-forward-harness/digest-testnet-shape.log b/docs/design/genesis-forward-harness/digest-testnet-shape.log new file mode 100644 index 00000000..c8c6590c --- /dev/null +++ b/docs/design/genesis-forward-harness/digest-testnet-shape.log @@ -0,0 +1,32 @@ +Signature scheme byte from the override file: genesis scheme 0 (0 = BLS12-381); every vote item and key reveal carries its scheme byte on the wire from DAA score 0; any other scheme refused until a class the 95 percent signal moves to names it +Key succession (W5) from the override file: a vote key hands its window weight and its forfeit term to a successor key once, from checkpoint DAA score 0; the successor inherits the window +Latency ladder from the override file: rungs 27, 35, 53, [88], [173], [267] shadow passes, cache [512 MiB] beside them (brackets: inadmissible, never entered), active from epoch 0 (DAA score 0 rounded up to the epoch boundary at 0), one rung per decision at 90 percent of blue blocks in each of 7 consecutive windows of 86400 DAA ending at an epoch's seed block +Latency ladder active: rungs 27, 35, 53, [88], [173], [267] shadow passes, cache [512 MiB] beside them, this node signals none (header version bits 15 and 14; both set asks for the cache rung) +Program class signal inactive (the window or the floor is absent from the override file): headers carry block version 2 exactly +Fees on igneum-devnet-973: pgas table v0, B_p 30000000 pgas, S_p 7500000 pgas, floors 1000000000 wei per gas and 1000000000 wei per pgas; calibrated v1 from DAA score never +Consensus params digest: 60e842a738c7dea04543af93e990fb962618ace0b76b38013a63cb29dd45644a (exchanged in the p2p handshake; a peer with another digest is refused) +2026-10-07 15:13:26.156+02:00 [INFO ] igneumd/2.1.0-75810130 +2026-10-07 15:13:26.156+02:00 [INFO ] Application directory: gf-digest/d-testnet-shape +2026-10-07 15:13:26.156+02:00 [INFO ] Data directory: gf-digest/d-testnet-shape/igneum-devnet-973/datadir +2026-10-07 15:13:26.156+02:00 [INFO ] Logs to console only +2026-10-07 15:13:26.172+02:00 [INFO ] Finality v2 (igneum-devnet-973): interval 30 depth 20 window 7200 DAA dust 5 presence 20 aggregators 8 ban 7200 fold 3; rule v3 (frozen table, certificate fold) from checkpoint DAA never; 0 checkpoints known, next index 1, 0 locks, 0 keys; C1 in DAA seconds from DAA never; weight-gated deep fork choice from DAA never; leave rule (W7, delay 3600 DAA) from checkpoint DAA never, 0 leaves; key succession (W5) from checkpoint DAA 0, 0 successions; signature scheme 0 (0 = BLS12-381), any other refused until a class names it +2026-10-07 15:13:26.180+02:00 [INFO ] [igneum-exec] proving v0: payouts from DAA score never, window 7200 DAA, dust 5, verifier Off +2026-10-07 15:13:26.180+02:00 [INFO ] [igneum-exec] proving v1: segment records from DAA score never, 8 blocks a segment, unproven after 600 DAA, aggregator share 1000 bps, shard program id unknown, aggregator id unknown +2026-10-07 15:13:26.180+02:00 [INFO ] [igneum-exec] proof archive at gf-digest/d-testnet-shape/igneum-devnet-973/datadir/evm/proofs: 0 proofs +2026-10-07 15:13:26.181+02:00 [INFO ] [igneum-exec] verifying keys embedded: shard program id 0x2b1a81cb413236cf063077b46ed3111628f6c41036bcf6e23ee4cbbf5679ef7a aggregator id 0x474678f35f7545db28055d5e5bbc308231d84a5a072202087a2a8d5b09123896 +2026-10-07 15:13:26.185+02:00 [INFO ] template prewarm: on (the cached block template is rebuilt on every virtual change; IGNEUM_TEMPLATE_PREWARM=0 turns it off) +2026-10-07 15:13:26.186+02:00 [INFO ] GRPC Server starting on: 127.0.0.1:29760 +2026-10-07 15:13:26.186+02:00 [INFO ] P2P Server starting on: 127.0.0.1:29761 +2026-10-07 15:13:26.186+02:00 [INFO ] [igneum-exec] eth_ JSON-RPC listening on 127.0.0.1:26790 +2026-10-07 15:13:26.186+02:00 [INFO ] WRPC Server starting on: 127.0.0.1:29762 +2026-10-07 15:13:26.186+02:00 [INFO ] [igneum-exec] chain follower started +2026-10-07 15:13:26.186+02:00 [INFO ] [igneum-exec] proof verifier: Off (no SP1 verification on this node) +2026-10-07 15:13:26.186+02:00 [WARN ] [igneum-exec] cannot bind the eth_ JSON-RPC server on 127.0.0.1:26790: Address already in use (os error 98) +2026-10-07 15:13:26.186+02:00 [INFO ] [igneum-exec] exec sync: sink edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block 0; pruning point edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block Some(0) (DAA 0); retention root edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 is chain block Some(0) (DAA 0), its body held +2026-10-07 15:13:26.186+02:00 [INFO ] [igneum-exec] exec sync: no snapshot to resume from (no snapshot file); the follower starts at genesis +2026-10-07 15:13:26.186+02:00 [INFO ] [igneum-exec] chain follower stopped +^SIGTERM - shutting down... +2026-10-07 15:13:51.152+02:00 [INFO ] P2P Server stopped: 127.0.0.1:29761 +2026-10-07 15:13:51.152+02:00 [INFO ] WRPC Server stopped on: 127.0.0.1:29762 +2026-10-07 15:13:51.152+02:00 [INFO ] GRPC Server stopped on: 127.0.0.1:29760 +2026-10-07 15:13:51.660+02:00 [INFO ] igneumd has stopped... diff --git a/docs/design/genesis-forward-harness/known-failed-no-succession.json b/docs/design/genesis-forward-harness/known-failed-no-succession.json new file mode 100644 index 00000000..461105de --- /dev/null +++ b/docs/design/genesis-forward-harness/known-failed-no-succession.json @@ -0,0 +1,695 @@ +{ + "expect": "no-succession", + "pass": false, + "checks": { + "window_filled_before_the_succession": true, + "succession_accepted_on_submit": true, + "carried_once_on_every_node": false, + "successor_inherits_on_every_node": false, + "old_key_handed_over_on_every_node": false, + "nodes_agree_on_the_successor_weight": false, + "refused_old_again_on_n2": false, + "refused_old_again_on_n0": false, + "refused_successor_that_handed_over": false, + "scheme_1_refused_by_every_node": true, + "locks_continue_after_the_fold": true, + "zero_rejected_by_nodes": true, + "sinks_agree": true + }, + "old_key": "e4036b1e79c817f2", + "new_key": "2f031ed35a29ced8", + "old_blocks_before": 41, + "succeeded_at_daa": 261, + "carried_seen_at_daa": null, + "new_mined_alone": 112, + "rows": [ + { + "new": { + "blocks": 37, + "keyHash": "2f031ed35a29ced8", + "participation": 1, + "pubkey": "80ab38c523736ad8944c7d3375e7563ba3b0d5a3ab149bec568cf207edcc9e1ab1ee2566e06ff422a5ec4c85a4049976", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "", + "voter": true + }, + "old": { + "blocks": 0, + "keyHash": "e4036b1e79c817f2", + "participation": 0, + "pubkey": "b0fd8eebbbaad96268e5f7b8a86090ca0c723954a0a5cb81c92b633094c1742b2505c4f7cea646c5c96bb52e57400067", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "6543d321fd61aedb", + "voter": false + } + }, + { + "new": { + "blocks": 0, + "keyHash": "2f031ed35a29ced8", + "participation": 0, + "pubkey": "80ab38c523736ad8944c7d3375e7563ba3b0d5a3ab149bec568cf207edcc9e1ab1ee2566e06ff422a5ec4c85a4049976", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "e4036b1e79c817f2", + "voter": false + }, + "old": { + "blocks": 0, + "keyHash": "e4036b1e79c817f2", + "participation": 0, + "pubkey": "b0fd8eebbbaad96268e5f7b8a86090ca0c723954a0a5cb81c92b633094c1742b2505c4f7cea646c5c96bb52e57400067", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "6543d321fd61aedb", + "voter": false + } + }, + { + "new": { + "blocks": 37, + "keyHash": "2f031ed35a29ced8", + "participation": 1, + "pubkey": "80ab38c523736ad8944c7d3375e7563ba3b0d5a3ab149bec568cf207edcc9e1ab1ee2566e06ff422a5ec4c85a4049976", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "", + "voter": true + }, + "old": { + "blocks": 0, + "keyHash": "e4036b1e79c817f2", + "participation": 0, + "pubkey": "b0fd8eebbbaad96268e5f7b8a86090ca0c723954a0a5cb81c92b633094c1742b2505c4f7cea646c5c96bb52e57400067", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "6543d321fd61aedb", + "voter": false + } + } + ], + "locks": [ + 12, + 12, + 12 + ], + "locks_at_fold": 9, + "refusals": [ + { + "what": "w5-old -> w5-third on n2", + "code": 0, + "line": "1791378723.612 SUCCEED old=e4036b1e79c817f2 new=6543d321fd61aedb daa=502 (accepted: carried in this node's next block)" + }, + { + "what": "w5-old -> w5-third on n0", + "code": 0, + "line": "1791378723.629 SUCCEED old=e4036b1e79c817f2 new=6543d321fd61aedb daa=502 (accepted: carried in this node's next block)" + }, + { + "what": "w5-new -> w5-old on n1", + "code": 0, + "line": "1791378723.645 SUCCEED old=2f031ed35a29ced8 new=e4036b1e79c817f2 daa=502 (accepted: carried in this node's next block)" + } + ], + "probes": [ + { + "node": 0, + "code": 0, + "line": "1791378723.659 SCHEME 1 REFUSED key=b875b095c1b5d559 index=16 (refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one)" + }, + { + "node": 1, + "code": 0, + "line": "1791378723.672 SCHEME 1 REFUSED key=b875b095c1b5d559 index=16 (refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one)" + }, + { + "node": 2, + "code": 0, + "line": "1791378723.687 SCHEME 1 REFUSED key=b875b095c1b5d559 index=16 (refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one)" + } + ], + "samples": [ + { + "t": 5.7, + "daa": 0, + "phase": "fill", + "weights": [ + "?", + "?", + "?" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 20.7, + "daa": 7, + "phase": "fill", + "weights": [ + "?", + "?", + "?" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 35.7, + "daa": 17, + "phase": "fill", + "weights": [ + "?", + "?", + "?" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 50.7, + "daa": 36, + "phase": "fill", + "weights": [ + "?", + "?", + "?" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 65.7, + "daa": 57, + "phase": "fill", + "weights": [ + "29/3", + "29/3", + "29/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 80.8, + "daa": 79, + "phase": "fill", + "weights": [ + "29/3", + "29/3", + "29/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 95.8, + "daa": 92, + "phase": "fill", + "weights": [ + "59/3", + "59/3", + "59/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 110.8, + "daa": 104, + "phase": "fill", + "weights": [ + "59/3", + "59/3", + "59/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 125.8, + "daa": 114, + "phase": "fill", + "weights": [ + "89/3", + "89/3", + "89/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 140.8, + "daa": 123, + "phase": "fill", + "weights": [ + "89/3", + "89/3", + "89/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 155.8, + "daa": 135, + "phase": "fill", + "weights": [ + "89/3", + "89/3", + "89/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 170.9, + "daa": 152, + "phase": "fill", + "weights": [ + "119/3", + "119/3", + "119/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 185.9, + "daa": 173, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 1, + 1, + 1 + ] + }, + { + "t": 200.9, + "daa": 187, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 1, + 1, + 1 + ] + }, + { + "t": 215.9, + "daa": 203, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 2, + 2, + 2 + ] + }, + { + "t": 230.9, + "daa": 224, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 2, + 2, + 2 + ] + }, + { + "t": 246, + "daa": 242, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 3, + 3, + 3 + ] + }, + { + "t": 261, + "daa": 258, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 3, + 3, + 3 + ] + }, + { + "t": 276.5, + "daa": 270, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 291.6, + "daa": 285, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 306.6, + "daa": 308, + "phase": "carry", + "weights": [ + "118/3", + "118/3", + "118/3" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 321.7, + "daa": 338, + "phase": "carry", + "weights": [ + "120/4", + "120/4", + "120/4" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 336.7, + "daa": 357, + "phase": "carry", + "weights": [ + "120/4", + "120/4", + "120/4" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 351.8, + "daa": 368, + "phase": "carry", + "weights": [ + "120/4", + "120/4", + "120/4" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 366.9, + "daa": 385, + "phase": "carry", + "weights": [ + "120/4", + "120/4", + "120/4" + ], + "locks": [ + 5, + 5, + 5 + ] + }, + { + "t": 382, + "daa": 406, + "phase": "carry", + "weights": [ + "120/4", + "120/4", + "120/4" + ], + "locks": [ + 5, + 5, + 5 + ] + }, + { + "t": 397, + "daa": 424, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 6, + 6, + 6 + ] + }, + { + "t": 412.1, + "daa": 441, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 7, + 7, + 7 + ] + }, + { + "t": 427.2, + "daa": 454, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 7, + 7, + 7 + ] + }, + { + "t": 442.3, + "daa": 469, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 7, + 7, + 7 + ] + }, + { + "t": 457.3, + "daa": 485, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 8, + 8, + 8 + ] + }, + { + "t": 472.4, + "daa": 494, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 8, + 8, + 8 + ] + }, + { + "t": 487.6, + "daa": 509, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 9, + 9, + 9 + ] + }, + { + "t": 502.6, + "daa": 526, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 9, + 9, + 9 + ] + }, + { + "t": 517.6, + "daa": 545, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 10, + 10, + 10 + ] + }, + { + "t": 532.7, + "daa": 561, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 11, + 11, + 11 + ] + }, + { + "t": 547.7, + "daa": 578, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 11, + 11, + 11 + ] + } + ], + "wall_s": 560.2, + "binaries": { + "igneumd": "/srv/builds/igneum-wt-genesis-forward/vendor/igneum-node-gf/target/release/igneumd", + "miner": "/srv/builds/igneum-wt-genesis-forward/vendor/igneum-node-gf/target/release/igneum-miner" + } +} \ No newline at end of file diff --git a/docs/design/genesis-forward-harness/pass-succession.json b/docs/design/genesis-forward-harness/pass-succession.json new file mode 100644 index 00000000..c3ecc7e7 --- /dev/null +++ b/docs/design/genesis-forward-harness/pass-succession.json @@ -0,0 +1,455 @@ +{ + "expect": "succession", + "pass": true, + "checks": { + "window_filled_before_the_succession": true, + "succession_accepted_on_submit": true, + "carried_once_on_every_node": true, + "successor_inherits_on_every_node": true, + "old_key_handed_over_on_every_node": true, + "nodes_agree_on_the_successor_weight": true, + "refused_old_again_on_n2": true, + "refused_old_again_on_n0": true, + "refused_successor_that_handed_over": true, + "scheme_1_refused_by_every_node": true, + "locks_continue_after_the_fold": true, + "zero_rejected_by_nodes": true, + "sinks_agree": true + }, + "old_key": "e4036b1e79c817f2", + "new_key": "2f031ed35a29ced8", + "old_blocks_before": 42, + "succeeded_at_daa": 260, + "carried_seen_at_daa": 266, + "new_mined_alone": 29, + "rows": [ + { + "new": { + "blocks": 37, + "keyHash": "2f031ed35a29ced8", + "participation": 1, + "pubkey": "80ab38c523736ad8944c7d3375e7563ba3b0d5a3ab149bec568cf207edcc9e1ab1ee2566e06ff422a5ec4c85a4049976", + "strippedUntilDaa": 0, + "succeededFrom": "e4036b1e79c817f2", + "succeededTo": "", + "voter": true + }, + "old": { + "blocks": 0, + "keyHash": "e4036b1e79c817f2", + "participation": 0, + "pubkey": "b0fd8eebbbaad96268e5f7b8a86090ca0c723954a0a5cb81c92b633094c1742b2505c4f7cea646c5c96bb52e57400067", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "2f031ed35a29ced8", + "voter": false + } + }, + { + "new": { + "blocks": 37, + "keyHash": "2f031ed35a29ced8", + "participation": 1, + "pubkey": "80ab38c523736ad8944c7d3375e7563ba3b0d5a3ab149bec568cf207edcc9e1ab1ee2566e06ff422a5ec4c85a4049976", + "strippedUntilDaa": 0, + "succeededFrom": "e4036b1e79c817f2", + "succeededTo": "", + "voter": true + }, + "old": { + "blocks": 0, + "keyHash": "e4036b1e79c817f2", + "participation": 0, + "pubkey": "b0fd8eebbbaad96268e5f7b8a86090ca0c723954a0a5cb81c92b633094c1742b2505c4f7cea646c5c96bb52e57400067", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "2f031ed35a29ced8", + "voter": false + } + }, + { + "new": { + "blocks": 37, + "keyHash": "2f031ed35a29ced8", + "participation": 1, + "pubkey": "80ab38c523736ad8944c7d3375e7563ba3b0d5a3ab149bec568cf207edcc9e1ab1ee2566e06ff422a5ec4c85a4049976", + "strippedUntilDaa": 0, + "succeededFrom": "e4036b1e79c817f2", + "succeededTo": "", + "voter": true + }, + "old": { + "blocks": 0, + "keyHash": "e4036b1e79c817f2", + "participation": 0, + "pubkey": "b0fd8eebbbaad96268e5f7b8a86090ca0c723954a0a5cb81c92b633094c1742b2505c4f7cea646c5c96bb52e57400067", + "strippedUntilDaa": 0, + "succeededFrom": "", + "succeededTo": "2f031ed35a29ced8", + "voter": false + } + } + ], + "locks": [ + 7, + 7, + 7 + ], + "locks_at_fold": 4, + "refusals": [ + { + "what": "w5-old -> w5-third on n2", + "code": 3, + "line": "1791379651.057 SUCCEED REFUSED old=e4036b1e79c817f2 new=6543d321fd61aedb daa=290 (refused: key e4036b1e79c817f2 has already handed its weight to 2f031ed35a29ced8; a key hands over once)" + }, + { + "what": "w5-old -> w5-third on n0", + "code": 3, + "line": "1791379651.072 SUCCEED REFUSED old=e4036b1e79c817f2 new=6543d321fd61aedb daa=290 (refused: key e4036b1e79c817f2 has already handed its weight to 2f031ed35a29ced8; a key hands over once)" + }, + { + "what": "w5-new -> w5-old on n1", + "code": 3, + "line": "1791379651.085 SUCCEED REFUSED old=2f031ed35a29ced8 new=e4036b1e79c817f2 daa=290 (refused: successor e4036b1e79c817f2 has itself handed its weight to 2f031ed35a29ced8; it signs nothing and can inherit nothing)" + } + ], + "probes": [ + { + "node": 0, + "code": 0, + "line": "1791379651.097 SCHEME 1 REFUSED key=b875b095c1b5d559 index=9 (refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one)" + }, + { + "node": 1, + "code": 0, + "line": "1791379651.110 SCHEME 1 REFUSED key=b875b095c1b5d559 index=9 (refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one)" + }, + { + "node": 2, + "code": 0, + "line": "1791379651.123 SCHEME 1 REFUSED key=b875b095c1b5d559 index=9 (refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one)" + } + ], + "samples": [ + { + "t": 5.6, + "daa": 0, + "phase": "fill", + "weights": [ + "?", + "?", + "?" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 20.6, + "daa": 37, + "phase": "fill", + "weights": [ + "?", + "?", + "?" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 35.6, + "daa": 56, + "phase": "fill", + "weights": [ + "28/3", + "28/3", + "28/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 50.6, + "daa": 71, + "phase": "fill", + "weights": [ + "28/3", + "28/3", + "28/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 65.6, + "daa": 85, + "phase": "fill", + "weights": [ + "58/3", + "58/3", + "58/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 80.6, + "daa": 106, + "phase": "fill", + "weights": [ + "58/3", + "58/3", + "58/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 95.6, + "daa": 126, + "phase": "fill", + "weights": [ + "88/3", + "88/3", + "88/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 110.6, + "daa": 141, + "phase": "fill", + "weights": [ + "118/3", + "118/3", + "118/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 125.6, + "daa": 155, + "phase": "fill", + "weights": [ + "118/3", + "118/3", + "118/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 140.6, + "daa": 166, + "phase": "fill", + "weights": [ + "118/3", + "118/3", + "118/3" + ], + "locks": [ + 0, + 0, + 0 + ] + }, + { + "t": 155.7, + "daa": 185, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 1, + 1, + 1 + ] + }, + { + "t": 170.7, + "daa": 202, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 2, + 2, + 2 + ] + }, + { + "t": 185.7, + "daa": 213, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 2, + 2, + 2 + ] + }, + { + "t": 200.7, + "daa": 229, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 2, + 2, + 2 + ] + }, + { + "t": 215.7, + "daa": 244, + "phase": "fill", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 3, + 3, + 3 + ] + }, + { + "t": 233.2, + "daa": 261, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 248.3, + "daa": 280, + "phase": "carry", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 4, + 4, + 4 + ] + }, + { + "t": 263.4, + "daa": 300, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 5, + 5, + 5 + ] + }, + { + "t": 278.4, + "daa": 316, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 5, + 5, + 5 + ] + }, + { + "t": 293.4, + "daa": 328, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 6, + 6, + 6 + ] + }, + { + "t": 308.4, + "daa": 343, + "phase": "after", + "weights": [ + "120/3", + "120/3", + "120/3" + ], + "locks": [ + 6, + 6, + 6 + ] + } + ], + "wall_s": 317, + "binaries": { + "igneumd": "/srv/builds/igneum-wt-genesis-forward/vendor/igneum-node-gf/target/release/igneumd", + "miner": "/srv/builds/igneum-wt-genesis-forward/vendor/igneum-node-gf/target/release/igneum-miner" + } +} \ No newline at end of file diff --git a/docs/design/genesis-forward.md b/docs/design/genesis-forward.md new file mode 100644 index 00000000..e70f32e0 --- /dev/null +++ b/docs/design/genesis-forward.md @@ -0,0 +1,85 @@ +# Genesis forward-compatibility: the scheme byte, key succession, the cache rung + +7 October 2026, 10:1x UK, the project lead's order to build mission item 8 now (`docs/analysis/mission/mission.md` section 2.8; the research in `docs/analysis/mission/future.md` sections 1.3, 7 and 10 and `docs/design/finality-in-proof.md` section 7). Branch `genesis-forward` on the node fork (from release-0.3.19-node dc141409, the merged line that carries the ladder and the W7 leave item, which ca3-v4-0318 alone does not) and `genesis-forward` on the repo. Three genesis fields, every switch never on the devnet (its digest does not move), all three set at the testnet genesis by the testnet lane, which holds the cut and re-pins. The class-group VDF's quantum fallback is flagged in spec 04 section 4.8, not built. + +## 1. The scheme byte + +| Item | Place | Value | +|---|---|---| +| The byte | `finality::Vote::sig_scheme`, `finality::KeyReveal::sig_scheme`, `finality::Succession::successor_scheme` | `SIG_SCHEME_BLS12_381` = 0 everywhere today | +| The genesis value | `Params::sig_scheme` (override key `sig_scheme`) | 0 on every network | +| The switch | `Params::sig_scheme_activation_daa` (override key `sig_scheme_activation_daa`; `u64::MAX` = never) | never on devnet, simnet, mainnet; 0 at the testnet genesis | +| The digest | both fields enter once the switch is set (the 0.3.15 rule) | the devnet's digest unchanged; the testnet's moves at the cut | +| The wire, plain | vote item tag 1 (`Vote::LEN` = 280 bytes), evidence tag 3, reveal `IGNK` + 288 hex: scheme 0 implied | byte for byte what every live network carries | +| The wire, explicit | vote item tag 5 = `scheme \|\| vote`, evidence tag 6 = `scheme \|\| first \|\| second`, reveal `IGNS` + 2 hex of the scheme + 288 hex | written by every template once the switch is active (`encode_section_explicit_within`); a vote of any other scheme is always explicit, so a scheme the node does not run is never mistaken for one it does | +| The active scheme | `igneum::active_sig_scheme(genesis, class)` = the scheme the program class at the sink names (`SIG_SCHEME_OF_CLASS`, a genesis table with no row today), else the genesis byte; the finality manager re-reads it at every virtual change | 0 | +| The refusal | `FinalityManager::scheme_refusal`: a reveal is not registered, a vote is not recorded, carried or gossiped, a successor is refused; the RPC answers `refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one` | every node, whatever its switch | + +Why a class change names the scheme: the flip is the P2 mechanism (95 percent of mining weight over a window with a floor height, the one path a consensus change takes on this chain), and the aggregated-vote format must ship first (naive ML-DSA-44 votes at 8,192 voters cost 19.4 MB a checkpoint and 57 GB a day, `future.md` 7.3; with 250x SNARK aggregation about 223 MB a day). Adding a row to the table is a code change under the class path; the byte is in every item from genesis so the row costs no fork. + +## 2. Key succession, W5 + +| Item | Place | Value | +|---|---|---| +| The item | `finality::Succession` (tag 7, 297 bytes): `daa`, old key, successor key, successor scheme, the old key's signature, the successor's signature, both over `"igneum-succeed-v1/" \|\| chain_id \|\| 0 \|\| daa \|\| old \|\| new \|\| scheme` under `IGNEUM_SUCCEED_V1_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_` | the successor's signature is its consent and its proof of possession in one | +| The switch | `Params::finality_succession_activation_daa` (override key of the same name) | never everywhere; 0 at the testnet genesis; in the digest once set | +| Submission | `submitFinalitySuccession` (RPC op 158, gRPC 1131/1132, wRPC, `igneum-miner succeed `) | carried after the leaves in the templates of the node that holds it; no p2p gossip kind (the successor's own node mines it) | +| The fold | `successions_at` (deterministic like `leaves_at`: the lowest-DAA carrier in C's past dates it) and `fold_successions` inside `voters_at`: the old key's window blocks are credited to the successor as they stand (count and oldest block), the old key leaves the table, a ban or a leave on the old key lands on the successor; the successor's presence counts the old key's carried votes | from the first checkpoint whose past holds the carrier; the window inherited, not a fresh one | +| Once | one record per old key: a second succession from a key that handed over is refused (`already handed its weight to`), a successor that has itself handed over is refused (`has itself handed`), which also refuses every cycle; a chain old to new to newer is legal | a record outlives the weight it moved by one window, then is trimmed | +| After | the old key's votes are refused (`handed its weight to`), never counted, never carried | the old key is dead | +| The report | `getFinalityWeights`: `succeededFrom` on the successor's row, a weightless row for the old key with `succeededTo` | both nodes of the unit test agree on every voter list | + +Not in this round: a p2p gossip kind for successions (the leave's shape, protocol version bump); the app's "rotate key" button, which signs the item from the old key's label and restarts the miner under the new label (the `succeed` subcommand is the primitive); the aggregated-vote format. + +## 3. The cache rung beside N + +| Item | Place | Value | +|---|---|---| +| The rung | `igneum::CacheRung { mib, admissible }` as `LatencyLadder::cache_rung` (override key `latency_ladder_cache_rung`) | `{512, false}` at genesis; a power of two above the 256 MiB genesis cache | +| The signal | `LadderSignal::Cache` = both ladder bits set (header version 0xc000; until today both set was "no signal" and no node ever stamped it, so no block written so far changes meaning); `IGNEUM_LADDER_SIGNAL=cache` | code 3 in `PowEpochInfo::latency_ladder_signal` | +| The rule | `latency_ladder_step_signalled_with_cache`: the cache step moves 0 to 1 when every one of the newest seven windows reaches 90 percent for the cache rung, the rung is admissible and the cool-down holds (the oldest window begins after the last decision, shared with N); never back; never in the same decision as N (one signal per block, exclusive shares) | `LadderState::cache_step` | +| The digest | the rung's size and flag enter with the ladder, once `latency_ladder_activation_daa` is set | the testnet's digest moves at the cut | +| The RPC | `powEpoch.latencyLadderCacheStep`, `nextLatencyLadderCacheStep`, `latencyLadderCacheMib`, `nextLatencyLadderCacheMib`, `latencyLadderCacheBps`, `latencyLadderCacheWeakestBps`, `latencyLadderCacheAdmissible` (proto fields 37 to 43); the daemon's ladder line (`cache [512 MiB]`) | the rung visible on every node | +| The gate | `admissible` in the genesis list, false until measured: the cold verify of one warp with the 512 MiB cache on the reference core with its SMT sibling loaded under 10 ms (the verifier reads the cache, so this is the bound), the day-cache build on a 2019-class core under twice today's, and the 8 GB tier still holding dataset, cache and the prover footprint | the rule never enters an inadmissible rung (tested) | + +Why beside N and not a seventh N rung: the six approved rungs and their indices stand (the project lead, 7 October 2026, 09:3x UK); a rung inserted in the list would either sit behind the three inadmissible rungs (unreachable) or shift the approved indices. A second lever on the same signal carrier keeps the list as approved and the cool-down shared. + +Owed with the measurement: the engine's consumption of `cache_mib` (`EpochSeeds` carries `shadow_reps` today and no cache size; the pack and the three hosts build a 256 MiB cache), so the flag stays false until the path exists and is measured. Per tier what the rung means: every tier from 8 GB holds a 512 MiB cache; the day-cache build doubles (about 0.7 s on the reference core today, approximate, from the class v4 fill line); the Apple tier's unified memory holds it; a pool user does nothing. + +## 4. The class-group VDF under a quantum computer + +Flagged in spec 04 section 4.8, not sized: Shor computes the class-group order, which removes the sequentiality assumption of the Wesolowski VDF, so an attacker with a cryptographically relevant quantum computer grinds the hourly seed (a liveness nuisance against the lottery, not a safety break; finality rests on the vote keys of section 1). The fallback is a hash-chain delay behind the same version byte, designed when the scheme flip is scheduled. + +## 5. Gates + +| Gate | Where | Result | +|---|---|---| +| The digest test | `consensus_digest_covers_every_consensus_field_and_nothing_else` (30 edits; the scheme byte and the cache rung alone move nothing), `override_params_carry_the_genesis_forward_fields_and_the_digest_moves_only_when_set` | section 6 | +| Scheme 1 refused by every node until the signal | unit: `a_vote_reveal_or_successor_of_another_signature_scheme_is_refused_until_a_class_names_it` (RPC, in a block, a reveal, a successor; the explicit template form); fast-time: `probe-scheme` on three nodes | section 6 | +| A succession carried once and refused twice | unit: `a_key_hands_its_window_to_a_successor_once_and_a_second_succession_is_refused` (two nodes); fast-time: `infra/fast-time/key-succession.mjs` (the known-failed case `--expect no-succession` first) | section 6 | +| The ladder rung visible in the RPC | `powEpoch.latencyLadderCache*` on `getBlockTemplate`, the daemon line; unit: `latency_ladder_rule` (an inadmissible cache rung never entered; with the flag, 90 percent in seven windows enters it, N still steps after it, one rung, never back) | section 6 | +| The codec | `scheme_byte_and_succession_items_round_trip_and_an_older_decoder_stops_at_them` | section 6 | + +## 6. Results (7 October 2026, 12:5x to 14:3x UK; igneum-build-1 for the gate build, the harness and the digests, igneum-build-2 for the suites) + +| Gate | Run | Result | +|---|---|---| +| The digest test | `cargo test --release -p kaspa-consensus-core --lib` on build-2 at 75810130 | 124 passed, 0 failed, 2 ignored (`consensus_digest_covers_every_consensus_field_and_nothing_else` with 30 edits, `override_params_carry_the_genesis_forward_fields_and_the_digest_moves_only_when_set`, `latency_ladder_rule` with the cache rung) | +| The 60x keeper test | `fast_time_60x_file_is_the_devnet_at_60x` on build-2 against the completed file (repo 9c9a1f52) | 1 passed (the file had lacked 17 fields on master, `emission` and `proving_consensus_verify_daa` among them; the box mirrors carry no `infra/`, so the test skips there unless the file is shipped) | +| Scheme 1 refused by every node | unit `a_vote_reveal_or_successor_of_another_signature_scheme_is_refused_until_a_class_names_it`; fast-time `probe-scheme` on three nodes, both harness cases | green in the suite runs; every node: `SCHEME 1 REFUSED ... (refused: vote carries signature scheme 1; the active scheme is 0 (BLS12-381); another scheme is named only by a program class the 95 percent signal moves to, and none names one)` | +| A succession carried once and refused twice | unit `a_key_hands_its_window_to_a_successor_once_and_a_second_succession_is_refused` (two nodes); fast-time `infra/fast-time/key-succession.mjs` on build-1 (3 nodes, one CPU miner each, override-60x with the three switches at 0) | known-failed case first (`--expect no-succession`, 13:04 to 13:13 UK, `genesis-forward-harness/known-failed-no-succession.json`): FAIL as it must on every carried-once check with the probe, the locks and the sinks holding. Pass case (`--expect succession`, node bdb34f62, 13:23 to 13:28 UK, `pass-succession.json`): PASS, every check holds: w5-old held 42 blocks at DAA 260; the succession accepted on n2 at DAA 261 and carried by a block at DAA 266 on every node; at the fold (DAA 290) every node reads w5-new 37 blocks (7 mined alone) and w5-old 0 with `succeededTo`; w5-old to w5-third refused on n2 and n0 (`already handed`), w5-new to w5-old refused on n1 (`has itself handed`); locks 4 to 7 on every node after the fold; 0 rejected blocks, sinks agree; 317 s wall | +| The cache rung visible in the RPC | `powEpoch.latencyLadderCache*` (proto fields 37 to 43); the daemon's ladder line | the testnet-shaped file prints `rungs 27, 35, 53, [88], [173], [267] shadow passes, cache [512 MiB] beside them` (`digest-testnet-shape.log`) | +| The codec | `scheme_byte_and_succession_items_round_trip_and_an_older_decoder_stops_at_them` | green in the suite runs | +| The digest, cross-binary | igneumd 75810130 against the 0.3.20 base dc141409 (bs0319's build), both with no file, devnet suffix 973 | both `079d8a7e736abbd4` (`digest-no-file.log`, `digest-base-dc141409.log`): the three fields at never move nothing; the ladder alone at 0 reads `772f9f8e3ef59ca1`, the testnet-shaped file (ladder at 0, scheme switch at 0, succession at 0, the cache rung) `60e842a738c7dea0`, on devnet params; the testnet lane's own number comes from `TESTNET_PARAMS` at its cut. The 0.3.18 line's `c562d70e` is behind the decimals, tail-emission and subsidy fields of the 0.3.19 and 0.3.20 lines, not behind these | +| The gate build | `tools/build-remote.sh --priority gate` on build-1 at bdb34f62 | igneumd 57,554,336 B sha256 6d0aa37b..., igneum-miner 10,240,768 B sha256 69fc3c63... (commit string carried) | +| The consensus suite | `cargo test --release -p kaspa-consensus` (all targets) on build-2 at f95178a1, twice | 114 passed, 0 failed, 3 ignored in the lib binary both times, the two moved targets 1 each; `cargo check -p kaspad` clean. Before the fix below the lib binary failed 1 of 114 on one of the two-node tests in three of four runs (the succession test once, the pre-existing F23 test twice), node 1 refusing block 61 at DAA 60 with `UnexpectedDifficulty`, the two nodes' bits about 17,000 apart in the mantissa | + +Faults found on the way, both mine: (1) rule v3's frozen table stood at the lock before the carrier, where the old key still weighed and signed nothing, so nothing locked after the fold (fixed in `frozen_table`, 9322cc1d); (2) the template read the explicit-form switch from the process-wide static that only the daemon installs, so `TestConsensus` wrote the plain form (the manager holds the activation now, daa61847). + +### 6a. The two-node refusal: found and fixed (f95178a1) + +The probe on build-2: F23 alone three times, green each time; the three two-node tests together three times, green each time; so the refusal needed the rest of the lib binary. The cause: `consensus/src/consensus/services.rs` built the difficulty manager with `igneum::pow_epoch_blocks()`, the process-wide static, where every other argument of that constructor comes from `params`; two pruning-proof tests in the same binary (`igneum_m20_tests.rs:27`, `igneum_pow.rs:346`) install a 60-block schedule process-wide, so a `TestConsensus` built inside that window read a 60-block epoch into its difficulty manager while its partner read 3,600, the two disagreed on `reference_window` from DAA 60, and the second node refused block 61, the exact block of every refusal. The fix is the manager's own field, `params.pow_epoch_blocks`. The node's behaviour is unchanged (the daemon installs the static from the same params before building the consensus); the class is the static-at-construction read, the sibling of the signal-table race the node lane moved two installing tests for on 7 October 2026. The test helper now names the node and the block it refuses, which is what found it. + +## 7. For the testnet lane + +Override keys and testnet values: `sig_scheme: 0`, `sig_scheme_activation_daa: 0`, `finality_succession_activation_daa: 0`, `latency_ladder_cache_rung: {"mib": 512, "admissible": false}` (the six N rungs unchanged). Digest: with the ladder already active from genesis the cache rung's two fields enter after the six rungs' fields; the scheme switch adds two fields, the succession switch one. `print_testnet_object` prints the four keys. diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index 1a812545..9d68c4d5 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -308,7 +308,7 @@ Sweep (5 October 2026, evening): stated. `site/litepaper.html`, Finality ends wi ### F11. VDFs are exotic "A class-group VDF with Wesolowski proofs in a consensus-critical path, in a project with no cryptographer. Chia needed years and still got timelord ASICs." -Status: Answered by design, with the dependency conceded. +Status: Answered by design, with the dependency conceded. Update 7 October 2026 (era VDF lane): the era VDF is in the node (spec 4.4 Implemented, behind `era_vdf_activation_daa`, never until the project lead sets it per network), on a fixed-width integer with no C library, with the hash-chain fallback behind the genesis scheme byte for the day a class group's order is computable; the attack pass's F7 harness fires against the stand-in (1 of 6 cuts re-rolled at no delay) and is silent against the VDF (0 of 6); the measured rates, prove and verify times and the margin against the fastest known prover (chiavdf's AVX-512 path on the same box) are in `docs/analysis/era-vdf-2026-10-07.md`. The timelord-ASIC point is answered by the margin table of spec 4.6: the delay only has to exceed the 2-s publish window, and it does so by orders of magnitude on the fastest evaluator measured. The external review (O-4.1) is still owed. Was: Answered by design, with the dependency conceded. Answer: The VDF is used for one thing: making the hourly program unknowable within the roughly two seconds a miner has to decide whether to publish a block, closing a withhold-or-publish grind the review measured at about 130 to 1 for a 30% miner. The VDF input is a certified checkpoint at least one epoch before the epoch starts, so honest nodes have about 50 minutes of slack to evaluate a 10-minute VDF, and an evaluator 300x faster than reference would still be needed to beat the two-second decision window. Chia has run class-group VDFs in production since 2021, with faster hardware evaluators existing and not breaking it (approximate, from memory). The design doc lists the VDF as a new dependency and ships the evaluator in every node. The grinding simulation with and without the VDF is scheduled before gate 3. @@ -2347,6 +2347,30 @@ Evidence: `site/index.html`; `site/litepaper.html`; `tools/ci/ledger-text-check. - **E5, L9** (the client dev fee), evening: the fee is built, switchable and audited (E18). The homepage's miner section now carries the litepaper's sentence (`site/index.html`, under the download buttons: "The protocol carries no fee. The Igneum Miner software takes an optional 1% dev fee, like other GPU miners, off with one flag"), and the litepaper's "What a miner's hour looks like" states the mechanism, the flag and the audit. The "no value" and "may be reset" devnet wording of L9 is still open. - **D2** (the app share). The "Why build here" text is applied at `site/litepaper.html:355` and states the number; two gaps noted under E17. +## Node findings (6 October 2026, evening): the 0.3.15 canary and the sync crash, fixed before the cut + +Two faults found on the live devnet by the fleet's canary boxes and the hub while 0.3.15 (class v4) was held for its cut. Both are conceded as ours, both are fixed on the fork branch `ca3-v4-order-fix` for release-0.3.15-node (the shipper's tree), both carry a test and a gate that every future node cut runs (`infra/fast-time/node-compat.mjs`). + +### N1. A 0.3.15 node on the live file wrote blocks every 0.3.14 node rejected + +"p2-3090-1 on 713ef876 with the live thirteen-field file, at every reconnect since its 19:42Z restart: P2P, got reject message: wrong block version: got 1026 but expected 2 from peer 188.245.5.161:26611; blocks 122,630, synced false, peers 0." + +Status: Fixed (6 October 2026, 20:0xZ, fork commit 17c60367 on ca3-v4-order-fix, the stamp; and 20:4xZ, f1ea7a38, the receive side, after the clean canary of 21:3x UK showed a 0.3.15 node ACCEPTING and relaying a version-1026 block it would never write, off a poisoned peer, and being disconnected by every 0.3.14 peer in turn; in 0.3.15). Conceded: the node lane's fault, twice. + +Answer: the class v4 signal (PROPOSED, `docs/plans/counter-asic-3-node.md` section 6) is the producer's object version in the high byte of the header version; the first 0.3.15 build stamped it from the binary alone, so on the live file (no window, no floor) a new node wrote version 1026 and every 0.3.14 node, whose rule is version 2 or reject, refused its blocks and its relays, although the digest compat rule (the same evening) made the handshake peer. A new miner lost every block; a lagging new node could not re-sync. The fix gates the stamp, the signal read AND the header version rule on publish 2's object (both `program_class_v4_signal_window_daa` and `program_class_v4_activation_daa` set): on the thirteen-field file the header is byte for byte what 0.3.14 writes, and a header carrying the signal bit is refused with 0.3.14's own `WrongBlockVersion(1026, 2)` before the engine and never relayed, so a poisoned peer cannot poison a new node. What no new-node change can do: a 0.3.14 node whose datadir holds version-1026 blocks is disconnected by its 0.3.14 peers by THEIR rule when it relays them, until those blocks leave the relay window; the fleet wipes the poisoned datadirs. Why the gates missed it: the digest compat harness peered the two binaries but neither mined; the new gate mines on both sides, joins a clean node through each, restarts the new node and re-syncs it. + +Evidence: unit tests `block_template_uses_current_block_version` (version 2 with no window and no floor, 1026 with both) and `program_class_signal_rule`; the gate `infra/fast-time/node-compat.mjs` on the fixed binary beside 0.3.14 on the live object: one digest on all four nodes, the new miner's 73 blocks accepted by the old hub, counts 169/169/169/169, header versions {0: the genesis, 2: 169}, no reject line, the clean join and the restart re-sync (PASS, 20:06Z; `docs/plans/counter-asic-3-gate/node-compat-20261006-fixed.json`); the same gate on the canary binary 713ef876 reproduces the fault (`HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting`, counts 138 against 101, the restarted node never re-synced: FAIL, `node-compat-20261006-canary-failed-case.json`). + +### N2. Any peer could crash any pruned node with a sync request below its retention + +"thread 'tokio-rt-worker' panicked at consensus/src/processes/sync/mod.rs:87:62: called Result::unwrap() on an Err value: KeyNotFound(GhostdagCompact/0/edc4fa84...) then Exiting..." (the hub, a pruned 0.3.14 node, 19:57Z, when p2-3090-1, cut off since 19:42Z, began a sync from the genesis against it). + +Status: Fixed (6 October 2026, 20:2xZ, fork commit 7961c5f1 on ca3-v4-order-fix; in 0.3.15). Conceded: a remote crash vector in every release from the first pruned devnet node to 0.3.14; security. + +Answer: `SyncManager::antipast_hashes_between` (the IBD headers path, `RequestHeaders`) unwrapped the GHOSTDAG reads of the requested low block and of every chain block of the walk; a pruned node holds no GHOSTDAG data below its retention, so a request from the genesis killed the serving node, not the requester. The fix makes the walk fallible: a read below the retention is `SyncManagerError::BlockBelowRetention(hash)` (also in `find_highest_common_chain_block` and the pruning-point locator), the consensus API returns it, and the serving flow answers the peer with the error and disconnects it, the node alive; the peer syncs from a node that holds the history. The hub was restarted by hand at 20:00Z (synced in 25 s). + +Evidence: unit test `a_sync_request_below_retention_is_an_error_not_a_panic` (a chain of six headers, the genesis's GHOSTDAG entry deleted as a pruned node holds it, `get_hashes_between(genesis, tip)` returns the error, a request inside the retention still answers); the gate's served-join case (a clean old node syncing from the genesis through the NEW node, which must stay alive and serve it) in `node-compat.mjs`; the box's kaspa-consensus and consensus-core suites on the commit. A truly pruned serving node in a harness needs hours of fast-time chain (pruning depth 13,838 DAA) and is owed; the unit test stands in for it. + ## Status updates, 5 October 2026 (ledger sweep, night of 4 to 5 October) `docs/review/ledger-sweep-2026-10-05.md` holds the runs, the commands and the running table. Every Open, Proposed, Unmeasured or pending entry was read against its experiment line; the status lines above carry the evidence inline, marked "Sweep (5 October 2026)" where a note was added and "Was:" where the status changed. Items owned by the other two night branches (F23, F24, G12, X18, the flood memory growth; the base-fee floor, testnet parameters, G13, G14, public text) were left to them. @@ -2431,6 +2455,52 @@ Answer: Correct as a fault, wrong as a null. The window layer (spec 01 1.13.1 as The amendment (a0aaca92): in class v4's chain draw a load's source is drawn only from registers whose last writer injects (add, sub, xor, mad, shfl, load) or is a rotate (rotl, rotr), never one last written by `or`, `mul` or `mulhi` (`generator.rs` candidate_from_words_class, keyed on the era-composed V4_CLASS; no attempts lost, rule (a) unchanged; v2, v3 and the generator-2 ladder packs byte-identical). Split protection (the node lane's form): generator stays 4 and a generator-4 program id appends `"sub/" || PROGRAM_SUBVERSION_V4 (= 1) as little-endian u16` inside `program_id()`, so a binary from before the rule and one after it never share a program id for one seed and the node's id check catches a split; packs carry `IGNEUM_PROGRAM_SUBVERSION 1` and `"sub_version": 1`, and packcheck refuses a generator-4 pack whose sub-version is absent or other. The node side (release-0.3.20-node, built to a0aaca92): kaspa-pow's test pins 1a4230699a6b9c60 must-equal and c120d7963abdcd96 must-differ through `generator::program_id` with the suffix, and the amended class signals object byte 5 in the header (CLASS_SIGNAL_V4 = 5; the tally counts byte 5 and above), so a block of the 6 October stream (byte 4) never counts towards the flip and a byte-4 node forks alone at it; object 6 is class v5's. The seven gate packs re-exported (devnet epoch 0 and eras 0 to 5): id 1a4230699a6b9c60 (was c120d7963abdcd96, pinned as the must-differ vector in `tests/recheck.rs`), fingerprints Metal = Apple OpenCL 867dbc45cfb36b4d, 2146ecacc8c75a8e, fe52602393f6d3d4, 3b206471a13912b4, c3f03c4a5d7333aa, f1dfd7209f15bb97, 8c194da64fadf31d; the v3 control 73bcbfe8ccf988f1 / 90f794dd556f7a3b untouched; the zip of the eight packs sha256 889ec99976d2728b4b5035bfa476032e5b6a13b928968fc45236d5f25084aa39. -Evidence: `docs/analysis/ca3-v4-uniform.md` (the model, the census `tools/ca3-v4-uniform`, the chip pricing); F8's logs under `/srv/builds/igneum-wt-attack/target-attack-f8/log/`; the tests limited by the project lead's word to what prevents a split and proves the fix: the vectors (the seven packs' ids and fingerprints above), the `igneum-pow` crate suite on igneum-build-1 (SUITE-LINE), the pairing of the fork's kaspa-pow with this igneum-pow on igneum-build-1 (PAIRING-LINE), and one G1 run on the RTX 5090 (PC 2 job run-ca3-v4-amend-g1-pc2-20261007, 09:41:07 to 09:41:28Z, exit 0, the installed 0.3.17 worker sha256 14b6637e..., NVRTC, beside the app's miner, the prover on, the lock held 09:40:22 to 09:41:51Z): self-test PASS on all eight packs and every 2^24 fingerprint equal to the Mac's (the seven above and the control 90f794dd556f7a3b), NVRTC 188 to 332 ms per pack, the 1 GiB build 38 to 49 ms. +Evidence: `docs/analysis/ca3-v4-uniform.md` (the model, the census `tools/ca3-v4-uniform`, the chip pricing); F8's logs under `/srv/builds/igneum-wt-attack/target-attack-f8/log/`; the tests limited by the project lead's word to what prevents a split and proves the fix: the vectors (the seven packs' ids and fingerprints above), the `igneum-pow` crate suite on igneum-build-1 at 8c728ca3 (`tools/build-remote.sh -- test --release`, rc 0, 77 s, 12:1x UTC: 61 lib + 7 derive + 4 mixer + 19 packs + 2 recheck + 7 scratch = 100 passed, 0 failed, the pinned v2 and v3 packs byte-identical and the v4 must-equal and must-differ ids as pinned), the pairing of the fork's kaspa-pow with this igneum-pow on igneum-build-1 (PAIRING-LINE), and one G1 run on the RTX 5090 (PC 2 job run-ca3-v4-amend-g1-pc2-20261007, 09:41:07 to 09:41:28Z, exit 0, the installed 0.3.17 worker sha256 14b6637e..., NVRTC, beside the app's miner, the prover on, the lock held 09:40:22 to 09:41:51Z): self-test PASS on all eight packs and every 2^24 fingerprint equal to the Mac's (the seven above and the control 90f794dd556f7a3b), NVRTC 188 to 332 ms per pack, the 1 GiB build 38 to 49 ms. + +Sub-version 2 (7 October 2026, afternoon; main's ruling B2: 0.3.20 ships sub-version 1 as object byte 5 untouched, and sub-version 2 is object byte 7, built on this branch at 07a809a7): the attack-pass lane's F8 census on sub-version 1 (64 chain-shaped seeds, the window-model control, 2^24 nonces) read 53 of 64 PASS and 11 FAIL at 1.2x, worst p31 at 29.27x, and named three residual classes, all a constant delivered through a writer the one-writer rule admits: saturation or zero preserved through rotl, rotr or a load after a saturated load (p6, p23, p26, p31, p34), zero from mulhi (p45), and the iteration boundary (an `or` at 63 feeding a load at 1, p11); p4, p10 and p25 at 1.28x to 1.57x are the window model's own tail. (An F9 hot-set census read on the same day was withdrawn by its author: its harness drew outside the rule and its metric counted the era's designed windows as hot; F8 is the one re-gate instrument.) Sub-version 2 closes the three: the draw takes a load's source only from a register fresh by dataflow (fresh at the start; a load keeps freshness only from a fresh source; add, sub, xor, mad, shfl from either operand; rotl, rotr from their operand; or, mul, mulhi never), keyed on the class v4 shape on every draw path; rule (a') of the acceptance rule runs that freshness to its fixpoint over the loop (base then shadow block) and rejects a candidate whose load reads an unfresh register in the steady state; rule (c') counts, per load site, the source values equal to 0 or all-ones over the 64 units' 16,384 evaluations and rejects at 164 or more. The devnet epoch-0 seed's attempt 0 is rejected and attempt 1 accepted: id a788661687db4bb3 (c120d7963abdcd96 and 1a4230699a6b9c60 the must-differ pair), fingerprints Metal = Apple OpenCL e370fb2080b7dbb1, b7237555d31fc3cf, b6b167fa15dfe2c9, 28bdf65eff33f2c4, e26d38c46f3f1b16, dd8fdf6ff4f59eed, 8bf40f5cb858d835 (13:01Z), the packs zip sha256 69c36772cd79e44e2ddd589466d9c64a94a13c9e970e9f27bd76feabb9b4581b; the seven 256-block ladder packs of `packs-ca3-shadow` re-exported under the rule (their measured rates stand as the old stream's). Nothing is proposed for sub-version 2 until the attack-pass lane's two gates on 07a809a7 are green (the 64-seed census under 1.2x on every seed, the hot-set census on the chain path); its suite, pairing (after the node lane's re-pin to byte 7) and G1 lines are added below as they land. The static census (`tools/ca3-v4-uniform`, box 2, 13:12Z) over 1,024 chain-shaped seeds plus F8's p1 to p3 at sub-version 2: 0 lossy-sourced load sites of 16,432 (14,329 injecting, 2,103 bijective), 0 programs with an `or`-, `mul`- or `mulhi`-sourced load, the no-era draw path giving the devnet epoch-0 seed the pack's own id a788661687db4bb3 (one stream on every path); the cost of rules (a') and (c'): 1.99 attempts per seed on average against 0.05 before (p2's seed took five), which is 2 ms of generation per rejected attempt on one core, nothing a miner or node notices. The crate suite at 526fa757 on box 2 (`tools/build-remote.sh --box 2 -- test --release`, route line "box 2 for class suite, priority normal", rc 0, 66 s, 13:15Z): 61 lib + 7 derive + 4 mixer + 19 packs + 2 recheck + 7 scratch = 100 passed, 0 failed, the pinned v2 and v3 packs byte-identical and the three v4 ids as pinned (a788661687db4bb3 equal; c120d7963abdcd96 and 1a4230699a6b9c60 differ). + +AP-F8-2 (7 October 2026, 13:xx UTC, the attack-pass lane on sub-version 2 at 07a809a7): chain-shaped seed igneum-f9/331672 exhausted the 32 attempts under rule (a') and the generator panicked, which on the chain is an epoch no node can draw, a liveness halt; the measured (a') plus (c') rejection rate of about two thirds per attempt puts the exhaustion probability at about (2/3)^32, 2e-6 per epoch seed (sub-version 1 exhausted 0 of 10^6). Main's ruling: the draw is total and no consensus path panics. Fixed at 8bdcbdd8 with the stream unchanged (re-export diff 0; the id a788661687db4bb3 and the fingerprints stand, so sub-version 2 keeps its number and the running censuses): the attempt cap of the class v4 shape is 256 (`MAX_ATTEMPTS_V4`; v2 and v3 keep 32), which puts the exhaustion probability under 1e-45 at a worst-case draw of about half a second on one core; after the cap the seed takes the last-resort program, deterministic and accepted as drawn, the candidate at attempt 256 with every `or`, `mul` and `mulhi` of the base program and the shadow block rewritten to `xor`, so every register stays fresh from the init words on and rule (a') holds by construction. The spec text for class v4 therefore reads: attempts 0 to 255 under rules (a), (b), (a'), (c) and (c'), then the last-resort program; the probability of reaching it is (r)^256 for a per-attempt rejection rate r, under 1e-45 at the measured r of about 2/3. Tests: `class_v4_draw_is_total_with_the_last_resort` (the last resort on real (a')-rejected candidates, every load fresh after it, no lossy op left, the chain path over 64 seeds without a panic, the cap per class). The 10^6-seed exhaustion count at the fixed commit is the attack-pass lane's measurement (its re-gate string is 8bdcbdd8); the 4,096-seed census with the attempt histogram (box 2, 13:33Z, `tools/ca3-v4-uniform --n 4096 --f8` at 8bdcbdd8): 4,099 chain-shaped programs, 0 lossy-sourced load sites of 65,584 (57,304 injecting, 8,280 bijective), 0 exhaustions, the accepted attempt geometric with ratio 0.674 (1,338 at attempt 0, 921, 620, 408, 274, 189, 127, 75, 55, 40, 16, 11, 12, 6, 5, then one each at 16 and 17; mean 1.98), so the measured per-attempt rejection rate is r = 0.674 and the exhaustion probability is 0.674^32 = 3e-6 under the old cap and 0.674^256 = 1e-44 under the class v4 cap. The crate suite at 8bdcbdd8 on box 2 (route line "box 2 for class suite, priority normal", rc 0, 80 s, 13:31Z): 62 lib + 7 derive + 4 mixer + 19 packs + 2 recheck + 7 scratch = 101 passed, 0 failed, the total-draw test included. G1 for sub-version 2 on a fleet RTX 5090 (main's order; the fleet lane, p1-5090, driver 580.173.02, sm_120, the box's Linux NVRTC worker `igneum-worker-cuda 1.0 (4 October 2026)` sha256 97e036e2..., which carries no program and compiles each pack's own text; the kit zip sha256 asserted before the put; 13:43:22 to 13:44:07Z): `--bench --batches 5 --batch-log2 24 --block-warps 1` on all eight packs, self-test PASS on every pack with the cache FNV 448274a57f508cbc and every 2^24 fingerprint equal to the Mac's (the control 90f794dd556f7a3b and the seven above); the rates (120 to 142 MH/s beside the box's own miner loop) are a reference only. The Windows G1 on PC 2 (job run-ca3-v4-sub2-g1-pc2-20261007b, published 13:46:33Z under the PC 2 lock, ran 13:46:36 to 13:46:50Z, exit 0; app 0.3.19, the installed worker sha256 14b6637e..., the RTX 5090 switched off by the runner's --cards-off before the script and restored on exit, igneum-worker-cuda running 0 before and after, the prover untouched): self-test PASS on all eight packs with the cache FNV 448274a57f508cbc, every 2^24 fingerprint equal to the Mac's and the fleet's (the control 90f794dd556f7a3b and the seven above), NVRTC 197 to 317 ms per pack, the 1 GiB build 22 to 34 ms, 115 to 130 MH/s with the card alone (a reference, 5 batches). + +AP-F8-2, the exhaustion half, FIXED-AND-PASSED at 8bdcbdd8 on the attack-pass lane's 10^6 chain-shaped seeds through the chain path (14:03:53Z): 0 exhausted, 0 panics, 4 seeds past attempt 31 (three at 32, one at 35; 4e-6, inside (2/3)^32), max attempt 35, no seed at the last resort; r = 0.67, mean 2.0 attempts per seed. + +Sub-version 2's hot-set half did not read green: F8's 64-seed gate at 39 of 64 had 8 over 1.2x of the window model (p23 4.82x, p19 3.32x, p15 2.57x, p18 2.50x, p34 1.25x; p4, p8, p10 unattributed at 1.22x to 1.50x). AP-F8-3 (7 October 2026, 14:0x UTC, the hash lane): the cause of the whole residual is that `accept.rs` never executed the latency-shadow block. Its interpreter (`run_unit`) was written for class v2 and v3 and ran the 64 base instructions per iteration and nothing after instruction 63, while the hash (`verify.rs`, the kernels) runs the shadow 27 times at the end of every iteration; so every dynamic acceptance test (c), (c') judged a class v4 program the chain never hashes. Main's word (14:1x UTC): 0.3.21 ships object byte 5 (sub-version 1); sub-version 3 is 0.3.22's and starts with this fix. Sub-version 3, first commit: `run_unit` executes the shadow block after instruction 63 of every iteration, `reps` times with the iteration's sel, as the hash does; the test `acceptance_executes_the_shadow_block_as_the_verifier_does` pins the acceptance's execution to `verify.rs` on the devnet epoch-0 program and the six test eras (the output bit counts over the 64 units equal, and different with the shadow stripped), so the two paths cannot diverge silently again; `PROGRAM_SUBVERSION_V4` = 3 (a new acceptance verdict is a new stream); the devnet epoch-0 seed still accepts at attempt 1, so its program and fingerprint are sub-version 2's (e370fb2080b7dbb1) under the new id a785001687d8688a (the must-differ set: c120d7963abdcd96, 1a4230699a6b9c60, a788661687db4bb3); the packs zip sha256 4f2445c50c58d76a5544023492d8b858d0b07c5e372d31f9c90c4ce51f829154. The class behind p23, localised from its program and reproduced in the acceptance's own execution: site 7 (instruction 38) reads r6 after 25 `mulhi r6`, 31 `or r6 |= r4`, 35 `xor r6 ^= r4`, which is `r6 & ~r4`, an AND mask the lineage rule counted as fresh because the xor's operand is the or's; over 2^20 evaluations on the closed-form words site 7 reads 874,953 distinct word indices against about 1,046,500 for every other site (0.84 of uniform; 2.2 s on one core), over 2^24 8,979,203 against about 16,260,000 (0.55; 35 s). The second sub-version 3 commit (held, prepared in the worktree) is a per-site distinct-index ratio against the uniform expectation of the site's window, its threshold set from the clean seeds' spread and its sample size from the cost line above; the dynamic bounds as first specified (a most-repeated-value bound at 16,384 and a distinct floor at 2^19.5 over 2^20) do not reach p23 and are not committed. The crate suite at ddacfbd3 on box 2 (route "box 2 for class suite, priority normal", rc 0, 41 s, 14:20Z): 63 lib + 7 derive + 4 mixer + 19 packs + 2 recheck + 7 scratch = 102 passed, 0 failed, the agreement test included. F8's final 64-seed table on sub-version 2 (the attack-pass lane, 14:2x UTC): 9 over 1.2x (p23 4.82x, p19 3.32x, p15 2.57x, p18 2.50x, p56 2.01x, p10 1.50x, p8 1.38x, p34 1.25x, p4 1.22x), the 55 clean seeds at 0.9915x to 1.144x; the 256-item bucket entropy over the window separates the strong four only (0.637 to 0.974 against a clean minimum of 0.9865 over 848 site rows), so the threshold of the second commit's distinct-index ratio comes from a run of that ratio on the 55 clean seeds at 2^20. The static census at ddacfbd3 (box 2, 14:22Z, 4,096 chain-shaped seeds plus F8's p1 to p3): 4,099 programs, 0 lossy-sourced load sites of 65,584, 0 exhaustions, the accepted attempt geometric as before (1,328 at attempt 0, 917, 622, 409, 284, ... one each at 16 and 17; mean 1.998, max 17), so the shadow-executed verdicts move a handful of seeds' attempts and nothing else; the devnet epoch-0 seed at attempt 1, id a785001687d8688a. + +Sub-version 3, second commit (7 October 2026, 14:44 UTC, the hash lane; the sub-version number stays 3 and the stream is unchanged: re-export diff 0 on the eight packs, the id a785001687d8688a, the seven fingerprints and the zip sha256 stand), two rules. The shared-operand rule, in the draw's source rule and in the acceptance's (a') pass: or-then-xor or or-then-sub on one operand is `d & ~s`, xor-then-or is `d | s`, so the second write leaves the register lossy although either op alone injects; any write to either register clears the relation. On p23 the chain's attempt 1 (id d65122675f16a1c7) now draws site 7 (instruction 38) from r5 and passes; the devnet epoch-0 seed still draws attempt 1, the same program. Rule (c''), the distinct-index ratio: over 4,096 units (2^20 evaluations per site, the closed-form words, the shadow executed) every load site's count of distinct word indices against the uniform expectation on its window (N - N^2 / 2W, the window 2^28 >> min(win, 2)) must reach 0.98 (`MIN_DISTINCT_RATIO_V4`), the last test of the chosen candidate; a candidate under it is rejected and the next attempt drawn under the 256 cap and the last resort. The floor from the 64-seed run at 2^20 (box 2): the 55 clean seeds' minimum over their site rows 0.9960 (p1 0.9990, median 1.0000); the strong five p23 0.8361, p18 0.9274, p19 0.9335, p15 0.9432, p56 0.9654; 0.98 sits 0.015 from each side. The chain's own candidates it refuses (the test `class_v4_distinct_ratio_rejects_the_low_entropy_band`, box 2, 2.1 to 2.2 s each): p15 attempt 3 id 52638ea2e8b0fd68 site 2 at 0.943, p18 attempt 2 id 9a37e9489d8ba698 site 6 at 0.927, p19 attempt 0 id 79d7441de0689223 site 15 at 0.933, p56 attempt 2 id 486a8ad2701ec3b5 site 2 at 0.965; p23's attempt 1 with site 7 put back to r6 is refused by (a') (`UnfreshLoadSource`) and, run anyway, by the ratio at 0.836 (874,928 distinct of 1,048,576). Main's rule for a staged 2^24 pass (taken only if 2^24 separates the weak four from the clean seeds by at least the 2^20 gap) was decided by the 2^24 lines (box 2, 35 s per seed on one core): the weak four p34 0.9181, p4 0.9614, p8 0.9630, p10 0.9612; the clean seeds p44 0.9612, p52 0.9613, p3 0.9971, p2 and p5 1.0004; p23's attempt 1 1.0004. Two clean seeds sit on the weak four's value, so a 2^24 floor that reaches the weak four rejects clean seeds, and the 0.961 that recurs on both sides is a band the ratio reads at 2^24 that F8's hot-set gate did not flag on p44 or p52. Committed: the ratio at 0.98 over 2^20 alone (`ACCEPT_UNITS_DISTINCT_V4` = 4096), no 2^24 stage. Open tail: p4, p8, p10 and p34 (1.22x to 1.50x on F8's gate) read 0.9927 to 0.9963 at 2^20, inside the clean spread; unattributed and chased. The (B) most-repeated-value bound stays in the file unwired (`MAX_SOURCE_REPEAT_V4`, `most_repeated`). Cost: the chosen candidate's acceptance gains one 2^20 pass, 2.1 to 2.2 s on one box-2 core, once per epoch draw per node. + +The second sub-version 3 commit is 017e70376489251e18564c0abce7e466e606c8b3 (pushed 15:10 UTC's preceding hour, pre-push gate GREEN, CI run 37639406567 success). The crate suite at 017e7037 on box 2 (rc 0, 184 s): 64 lib + 7 derive + 4 mixer + 19 packs + 2 recheck + 7 scratch = 103 passed, 0 failed, 3 diagnostics ignored; the lib tests took 140.8 s against ddacfbd3's 41 s for the whole suite, because every class v4 draw in the tests now pays the 2^20 pass on its chosen candidate (2.2 s each); that is CI time, not node time. The static census at 017e7037 (box 2, 15:1x UTC, 4,096 chain-shaped seeds plus F8's p1 to p3, the draws in parallel over the box's cores since the ratio pass makes the serial run a four-hour job): 4,099 programs, 0 lossy-sourced load sites of 65,584 (57,322 inject, 8,262 bijective), 0 exhaustions; the accepted attempt 1,297 at attempt 0, 899, 609, 416, 298, 197, 125, 92, 54, 46, 21, 14, 13, 9, 6, 0, 2, 1 (mean 2.086, max 17) against ddacfbd3's 1,328, 917, 622, 409, 284, ... (mean 1.998, max 17): the ratio refuses about 4 percent of the candidates that pass every other test, one more attempt on about one seed in twelve; the devnet epoch-0 seed at attempt 1, id a785001687d8688a; F8's p2 at attempt 5, p3 at attempt 1. The attack-pass lane's class check of ddacfbd3's shadow-executed verdicts against 8bdcbdd8 (598,678 chain-shaped seeds): 11,990 (2.0 percent) accept at a different attempt, 0 exhausted, max attempt 32; on 017e7037 the pairing holds (the devnet epoch-0 program draws as a785001687d8688a) and its 64-seed gate at 2^24 and 10^6 exhaustion count were running at 15:1x UTC (finish about 16:05 UTC). + +F8's 64-seed gate on 017e7037 (the attack-pass lane, box 2, last seed 16:00:20 UTC): 60 of 64 under 1.2x; the four over are the named tail and nothing else (p10 1.5036x, p8 1.3776x, p34 1.2505x, p4 1.2167x; hottest items 355 to 541 reads of 2^31, unattributed, inside the ratio's clean spread); p23, p19, p15, p18 and p56 under the line; the clean spread 0.9915x to 1.144x. Exhaustion on 017e7037: 0 in the attack-pass lane's 20,532 chain-shaped seeds (max attempt 29) plus this lane's 4,099 (max 17); the 10^6 count continues as a strengthening line. Cost line for the node: the chain's epoch draw now pays the 2^20 ratio pass on every candidate that reaches it (the accepted one, and the about 4 percent that fail there), 2.2 s per pass on one box-2 core, so about 2.3 s per epoch draw on that core and, by the attack-pass lane's reading, about 4 to 5 s per epoch per node on slower cores; the earlier rejections cost milliseconds. From the attack-pass lane sub-version 3 at 017e7037 reads green on both gates with the named tail; its close line went to the plan, main and the Counter ASIC lane. This row stays open on the tail (p4, p8, p10, p34) until it is attributed or ruled accepted. Owed (recorded, not run, by the project lead's word): G2 (the CPU verifier on 1,024 hashes per card) on the amended stream; G3 (the Metal fuzz, edge, stats and determinism runs) on the amended stream; the hash-rate ladder re-measure on the M5 Max and the RTX 5090 (the amendment changes the base program's source draws, not the op mix or the load count, so the latency-bound rows of `docs/analysis/latency-shadow-2026-10-06.md` are expected to hold within their spread; unmeasured); AMD (the RX 9070 XT, PC 1); the 2019-class verifier core (O-1.14); F8's phase E (the 64-seed dynamic census) on the amended stream, which is the attack-pass lane's and the test of the per-op table. The row reads FIXED-AND-PASSED only after phase E passes against the amended class. + +## Genesis forward-compatibility entries (7 October 2026, mission item 8, branch `genesis-forward`) + +The three genesis fields of `docs/analysis/mission/mission.md` section 2.8, built on the node fork branch `genesis-forward` (from release-0.3.19-node dc141409) and the repo branch `genesis-forward`; the design and the gates in `docs/design/genesis-forward.md`. Every switch is never on the devnet (its digest c562d70e... does not move); the testnet genesis sets all three (the testnet lane re-pins and re-digests). + +### GF1. A post-quantum signature scheme would need a hard fork, and every vote key is a public BLS12-381 point +"When a quantum computer comes, every BLS vote key is forged and the chain has no way to change the scheme without a fork of the kind you say you never need." + +Status: Fixed (7 October 2026). `sig_scheme` byte in every vote item and key reveal (`finality::SIG_SCHEME_BLS12_381` = 0), `Params::sig_scheme` and `Params::sig_scheme_activation_daa` in the digest once set; any other byte refused by every node (`scheme_refusal`) until a program class the 95 percent signal moves to names it (`igneum::sig_scheme_of_class`, empty today). Gate: the digest test `override_params_carry_the_genesis_forward_fields_and_the_digest_moves_only_when_set` and `consensus_digest_covers_every_consensus_field_and_nothing_else` (30 edits); a scheme-1 vote refused over RPC, in a block, and as a successor's scheme (`a_vote_reveal_or_successor_of_another_signature_scheme_is_refused_until_a_class_names_it`); the fast-time probe on three nodes (`infra/fast-time/key-succession.mjs`, `probe-scheme`). + +Answer: The byte costs nothing now and a fork later. The flip is a class change (spec 03 W1 as amended): the aggregated-vote format ships first (naive ML-DSA-44 votes at 8,192 voters cost 57 GB a day, `future.md` 7.3), the scheme second, both by the 95 percent signal with a floor height. Per tier at migration: a home miner on any card runs the updater and signs one succession item (GF2); nothing hardware-specific, the signature runs on the CPU. + +### GF2. A vote key cannot move: a miner who changes keys re-earns 30 days of weight, and so does the post-quantum migration +"Your weight is bound to a key. Rotate it, lose a month. So nobody rotates, and the one day everyone must rotate, finality pauses for a month." + +Status: Fixed (7 October 2026). W5 key succession implemented (spec 03 W5 as amended): a succession item signed by both keys, carried in blocks after the leaves, folds the old key's window blocks and forfeit term into the successor from the first checkpoint whose past holds the carrier (`successions_at`, `fold_successions`); once per key, a second succession refused, a successor that has itself handed over refused. Behind `finality_succession_activation_daa`. Gate: carried once and refused twice in the unit test on two nodes and in the fast-time harness. + +Answer: The successor inherits the window, not a fresh one, so a key rotation costs no weight and the migration of GF1 is one item per key. The old key signs nothing after; a buyer of a key gets the clean transfer (spec 3.11.5) and the seller's copy is worthless. + +### GF3. A 256 MB on-chip cache makes the lottery hash 2 to 3x cheaper for the card that has it, and the cache size is a constant +"The dataset is built from a 256 MiB cache. A datacentre part with a 256 MB last-level cache keeps the whole cache on die and skips the memory. Consumer cards cannot." + +Status: Fixed as a genesis lever, measurement owed (7 October 2026). The latency ladder carries one cache rung beside N (`LatencyLadder::cache_rung`, 512 MiB, `LadderSignal::Cache` = both ladder bits, the same 90 percent in seven windows, one rung, never back, in the digest with the ladder); inadmissible at genesis until the verifier bound and the 8 GB tier are measured (design doc section 3); visible in `getBlockTemplate`'s `powEpoch` (`latencyLadderCacheStep`, `latencyLadderCacheMib`, `latencyLadderCacheBps`, `latencyLadderCacheAdmissible`) and in the daemon's ladder line. + +Answer: Consumer LLC is 96 to 128 MB today and datacentre 256 MB (`chip-model-v3`, approximate), so the shortcut is a datacentre card's today and a consumer card's in a generation or two. The rung lets the miners double the cache by signal the day the shortcut shows on the hash-rate charts, without a fork; the measurement that admits it is the same verifier bound as every N rung. Per tier: a 512 MiB cache is held by every tier from 8 GB up; the day-cache build doubles (about 0.7 s on the reference core today, approximate); the verifier reads the cache, not the dataset, so the 10 ms bound decides. + +### GF4. The class-group VDF falls to the same quantum computer +"Shor computes the class-group order; your epoch seed is then grindable." + +Status: Conceded, flagged in spec 04 section 4.8 (7 October 2026); not sized. The fallback is a hash-chain delay behind the same version byte that moves the signature scheme; designed when the scheme flip is scheduled. + +Answer: A grindable hourly seed is a liveness nuisance against the lottery, not a safety break: finality rests on the vote keys (GF1), the seed on the VDF. The two flip together by one class change. diff --git a/docs/plans/build-server.md b/docs/plans/build-server.md index 06d5cd8b..a7e907d5 100644 --- a/docs/plans/build-server.md +++ b/docs/plans/build-server.md @@ -357,3 +357,48 @@ service. The layer tracks the repo branch `master`; until this work merges, the 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 ` installs then runs one job's 10-minute smoke and prints the summary. + +## 7. Spill-over between the boxes (7 October 2026, 15:0x UK) + +the project lead's reading at 15:02 UK: build-1 at load 139 / 114 / 90 with both slots held and a queue (the horizon lane waited 1 h 40 min) +while build-2 read 4.5 / 27 / 46 with both slots free. The class router (section 6) pinned each class to its box with no +spill-over. Now (lib.sh `bs_route_spill`, master from this commit): + +- The class is a PREFERENCE: a build, check or gate prefers box 1, a suite, bench or attack row box 2, a proving crate box 3. +- Before a run, the preferred box is read with one ssh (free slots of its slot count, 1-minute load). It takes the job when it + has a free slot and its load is at or under 64 (`BS_SPILL_LOAD`). Otherwise the other box (1 and 2 swap; 3 falls to 1) takes it + when THAT one qualifies; when neither does, the job queues on its own box. A box without a host file is never chosen; an + unreachable box reads as "down" and is skipped. +- The decision is the first route line of the run ("route: class suite prefers box 2; box 2 (free=0 slots=3 load1=70) is full or + over load 64: spilled to box 1 (free=1 slots=2 load1=20)"), the slot label carries "; spilled from box N", and the JSONL row + carries `"route": {"preferred", "box", "spilled", "reason"}` for the dashboard's job card. +- build-2 has THREE slots (its `slots` file reads 3 since 14:0x UK; provision.sh defaults a `-2` hostname to 3). Everything that + lands on box 2 runs at the bounded class, nice 10 on a 32-core band with -j 32, builds and gates included, so a suite beside + them keeps its number; since this commit a bounded run takes the band its slot owns (slot 0 the last 32 cores, slot 1 the 32 + below, slot 2 the 32 below that), so three bounded runs never share a core. A gate still takes its slot ahead of queued suites. +- `--box N` still pins. Self-test: tools/ci/route-spill-check.sh (thirteen cases through `BS_ROUTE_STATE_`, no ssh), in the gate. + +## 8. The measure file is retired: per-core leases and the quiet class (7 October 2026, 15:07 UK) + +Main's reading at 15:07 UK: on build-1 a sync-fuzz probe (the capacity lane's, SIGSTOPped since 09:45Z, no owner) held the global +measure flock for five and a half hours; beside it attack-f6's phase2b waited exclusive on the same file behind seven F2 solvers +holding it shared on cores 6-11,54-59, and every new shared taker (every build) queued behind the exclusive waiter: load 120, slots +free, nine waiting. On build-2 the era VDF bench held the file exclusive while pinned to one core and five jobs waited. One global +exclusive lock across unrelated measurements was the wrong design. Now: + +- `infra/build-server/lease.sh`, installed on every box at `/srv/builds/_bin/lease` (provision.sh; by hand on build-1 and build-2 + at 14:2x BST): `lease cores --label "..." [--owner ] -- ` takes a lease on THOSE CORES ONLY (one flock per + core, `_locks/core-`, a `wait-` file with the label while it waits), runs the command under nice 10 and taskset, and + releases. Nothing else is excluded. `lease quiet --label "..." --owner -- ` is the whole-box class: refused (exit + 73) while any slot or core lease is held, capped at 20 minutes, holder line with the owner in `_locks/quiet`. `lease status`, + `lease reap`. +- remote-run.sh: an unbounded run (nice 0, the full set) takes `quiet` shared and waits for it; a bounded run (suites, benches, + everything on box 2) never takes it. Every run keeps off leased cores (its set minus the `core-` flocks, said once). A run's + keeper refreshes its holder file's mtime every 20 s and calls `lease reap`: a holder of a lease, the quiet file or a slot whose + process has been STOPPED for 5 minutes is killed and its file cleared, one line each in `_log/reaped.log`. The keeper closes the + lock descriptors it inherits (an orphaned `sleep 20` held a slot and a worktree lock 20 s past the release). BR_MEASURE=1 is the + quiet class with the same refusals; it needs a named owner (IGNEUM_AGENT). +- The lanes' own `flock -s /srv/builds/_locks/measure -c "nice -n 10 taskset -c ..."` lines no longer hold anything a build + waits for; they become `/srv/builds/_bin/lease cores --label "..." -- `, and `flock -x .../measure` becomes + `lease quiet`. Self-tests: lease.sh --self-test and remote-run.sh --self-test-slots, run on build-1 by tools/ci/box-locks-check.sh + in the gate. diff --git a/docs/plans/counter-asic-3-gate/class-v4-20261006-rehearsal-PASS.json b/docs/plans/counter-asic-3-gate/class-v4-20261006-rehearsal-PASS.json new file mode 100644 index 00000000..84d8d87f --- /dev/null +++ b/docs/plans/counter-asic-3-gate/class-v4-20261006-rehearsal-PASS.json @@ -0,0 +1,86 @@ +{ + "gate": "P1 rehearsal: the class v4 activation on a fleet of real boxes (Counter ASIC 3.0)", + "verdict": "PASS", + "date": "2026-10-06", + "network": "igneum-devnet-400 (fleet-only, from the devnet genesis state)", + "object": { + "file": "docs/plans/counter-asic-3-gate/override-v4-rehearsal-1bps.json", + "fields": 17, + "pow_epoch_blocks": 600, + "pow_epoch_lead": 100, + "program_class_v4_signal_window_daa": 600, + "program_class_v4_activation_daa": 2400, + "threshold_bps": 9500, + "consensus_digest_on_devnet_400": "a15db4f0d6a5cc891582760ef5593e69014a5133261def84647cb7617528764f" + }, + "binaries": { + "igneumd": "847ddfd1ba376009 (PC 2 job build-20261006-174823, the signalling fork 0562a7f2)", + "igneum-miner": "37178aeb09bc3a24", + "worker_first": "0.3.14 hive igneum-worker-cuda 1.0 (4 October): refused the generator-4 pack, the fleet stopped at the flip", + "worker_resume": "Linux generator-4 igneum-worker-cuda from the release tree 563485b, sha256 97e036e23f4ace66..., from 19:15:47Z" + }, + "boxes": { + "mining": 15, + "seed": 1, + "stale_old_binary": 1, + "wave_pods_joined_as_v4_miners": 37, + "signalling_nodes_with_the_flip_line": 52 + }, + "timeline_utc": { + "first_block": "18:41:10", + "flip_daa_1202": "19:00:5x", + "stall_at_flip_old_worker_s": 90, + "fleet_stopped_old_worker": "19:04:14", + "resume_new_worker": "19:15:47", + "floor_daa_2400": "19:33:2x", + "sweep": "19:34:54" + }, + "flip_line": "Program class v4 by miner signal: epoch 2 (share 10000 bps over 600 DAA ending at seed block f0606e203183798a728488ba8ce38ad5f552f53e3a7ac7b5e65d41c23fac5bf3, threshold 9500 bps, 599 of 599 blue blocks)", + "program_ids": { + "epoch_1_v3": "b237a661a3c7f7c1", + "epoch_2_v4": { + "pack": "24304f0788ea9408", + "cli_v4": "24304f0788ea9408", + "cli_v3": "d8927052c764f00b" + }, + "epoch_3_v4": { + "pack": "cc266b4f5dbc3447", + "cli_v4": "cc266b4f5dbc3447", + "cli_v3": "de3a34f1f2130228" + }, + "epoch_4_v4": { + "pack": "634018bab5e5f283", + "cli_v4": "634018bab5e5f283", + "cli_v3": "170e3aa4b2f69d60" + } + }, + "checks": { + "flip_by_signal_identical_line_on_every_node": true, + "one_program_id_per_epoch_across_boxes_equal_cli_v4_differs_v3": true, + "pow_rejected_on_every_node": 0, + "miner_mismatched": 0, + "blocks_on_v4_on_every_box": true, + "stale_box_refused_by_digest": { + "refusing_peer_lines": 11, + "digest_mismatch_lines": 22, + "ever_a_peer": false, + "old_digest": "507f802b" + }, + "stale_box_on_full_file": "refuses at parse (unknown field program_class_v4_activation_daa) and exits", + "floor_crossed_with_v4_in_force": true, + "sweep_19_34_54": { + "heights": "2502 to 2509", + "blue": "2432 to 2440", + "sinks_moving_with_height": 4, + "fork": false + }, + "g2_serve_mode_epoch_2_pack": { + "found": 1024, + "distinct": 1024, + "digest": "7bf77506546730973a708ebfcf7d1f908f58ff9687b529e9ccca1d022f81e3e6", + "mac_recheck": "MATCH 1024 of 1024" + } + }, + "cut_critical": "an old worker refuses a generator-4 pack by design, so publish 2 (the sixteen-field file) waits until every node AND every worker in the fleet is 0.3.15's; a 0.3.13 node given the file exits at parse; publish 1 carries no handshake split under the digest-compat rule", + "live_devnet_side_effect": "11 live miners at about half rate 18:42Z to 19:04Z (their GPUs shared with the rehearsal worker); no box left the live devnet" +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/digest-compat-20261006.json b/docs/plans/counter-asic-3-gate/digest-compat-20261006.json new file mode 100644 index 00000000..a07042c7 --- /dev/null +++ b/docs/plans/counter-asic-3-gate/digest-compat-20261006.json @@ -0,0 +1,64 @@ +{ + "pass": true, + "checks": { + "new_and_old_on_13_fields_print_one_digest": true, + "the_16_field_digest_differs": true, + "n0_peers_with_n1_and_n2": true, + "old_node_peers_with_new": true, + "n3_has_no_peer": true, + "a_digest_refusal_line_exists": true + }, + "rows": [ + { + "node": "n0", + "binary": "new", + "fields": 13, + "digest": "a89be8a7e64b7d5a0d89d19c8837b3a8c2323fc277717e94c508eeb51a5e71e6", + "peers": 2, + "refusal": [ + "Application directory: /tmp/igneum-digest-compat/n0", + "Data directory: /tmp/igneum-digest-compat/n0/igneum-devnet-995/datadir" + ] + }, + { + "node": "n1", + "binary": "new", + "fields": 13, + "digest": "a89be8a7e64b7d5a0d89d19c8837b3a8c2323fc277717e94c508eeb51a5e71e6", + "peers": 1, + "refusal": [ + "Application directory: /tmp/igneum-digest-compat/n1", + "Data directory: /tmp/igneum-digest-compat/n1/igneum-devnet-995/datadir" + ] + }, + { + "node": "n2", + "binary": "old", + "fields": 13, + "digest": "a89be8a7e64b7d5a0d89d19c8837b3a8c2323fc277717e94c508eeb51a5e71e6", + "peers": 1, + "refusal": [ + "Application directory: /tmp/igneum-digest-compat/n2", + "Data directory: /tmp/igneum-digest-compat/n2/igneum-devnet-995/datadir" + ] + }, + { + "node": "n3", + "binary": "new", + "fields": 16, + "digest": "db9a85f9ff08f72f11f7ba496b3060189c32bdf6cb53e94c810a4cc25585487a", + "peers": 0, + "refusal": [ + "Application directory: /tmp/igneum-digest-compat/n3", + "Data directory: /tmp/igneum-digest-compat/n3/igneum-devnet-995/datadir" + ] + } + ], + "new_binary": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-0315fix/../igneum-node-ca3v4/target-ca3v4/release/igneumd", + "old_binary": "/Users/joshm/Projects/igneum/vendor/igneum-node/target-0314/release/igneumd", + "secs": 45, + "refusal_lines_rechecked": [ + "Refusing peer 127.0.0.1:51123: consensus params digest mismatch, local a89be8a7e64b7d5a0d89d19c8837b3a8c2323fc277717e94c508eeb51a5e71e6 remote db9a85f9ff08f72f11f7ba496b3060189c32bdf6cb53e94c810a4cc25585487a (the peer's override file, environment or build differs)", + "Refusing peer 127.0.0.1:51393: consensus params digest mismatch, local a89be8a7e64b7d5a0d89d19c8837b3a8c2323fc277717e94c508eeb51a5e71e6 remote db9a85f9ff08f72f11f7ba496b3060189c32bdf6cb53e94c810a4cc25585487a (the peer's override file, environment or build differs)" + ] +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/node-compat-20261006-canary-failed-case.json b/docs/plans/counter-asic-3-gate/node-compat-20261006-canary-failed-case.json new file mode 100644 index 00000000..11e38481 --- /dev/null +++ b/docs/plans/counter-asic-3-gate/node-compat-20261006-canary-failed-case.json @@ -0,0 +1,94 @@ +{ + "pass": false, + "checks": { + "one_digest_on_old_and_new": true, + "new_miner_blocks_accepted": true, + "old_miner_blocks_accepted": true, + "zero_rejected_by_miners": true, + "no_wrong_version_or_reject_line": false, + "every_header_version_is_the_block_version": false, + "old_nodes_hold_the_new_blocks": false, + "counts_agree_at_the_end": false, + "clean_new_node_joined_and_synced": true, + "restarted_new_node_resynced": false + }, + "new_binary": "/private/tmp/claude-501/-Users-joshm/cd75457f-4858-4f86-9634-7481ee056b7b/scratchpad/igneumd-713ef876", + "old_binary": "/Users/joshm/Projects/igneum/vendor/igneum-node/target-0314/release/igneumd", + "digests": [ + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2" + ], + "counts_after_mining": [ + { + "count": 97, + "sink": "7c26e545e4982ac9", + "daa": 97 + }, + { + "count": 101, + "sink": "11439a8867718a2b", + "daa": 101 + }, + { + "count": 97, + "sink": "7c26e545e4982ac9", + "daa": 97 + }, + { + "count": 97, + "sink": "7c26e545e4982ac9", + "daa": 97 + } + ], + "final": [ + { + "count": 138, + "sink": "360b8259384d8fc1", + "daa": 138 + }, + { + "count": 101, + "sink": "11439a8867718a2b", + "daa": 101 + }, + { + "count": 138, + "sink": "360b8259384d8fc1", + "daa": 138 + }, + { + "count": 138, + "sink": "360b8259384d8fc1", + "daa": 138 + } + ], + "accepted_old_new": [ + 138, + 100 + ], + "rejected_by_miners_old_new": [ + 0, + 0 + ], + "reject_lines": [ + [ + "HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting from peer 127.0.0.1:51290.", + "HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting from peer 127.0.0.1:51818.", + "HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting from peer 127.0.0.1:52104." + ], + [ + "P2P, got reject message: wrong block version: got 1026 but expected 2 from peer: 127.0.0.1:29831", + "P2P, got reject message: wrong block version: got 1026 but expected 2 from peer: 127.0.0.1:29831", + "P2P, got reject message: wrong block version: got 1026 but expected 2 from peer: 127.0.0.1:29831" + ], + [], + [] + ], + "header_versions": { + "0": 1, + "2": 138 + }, + "secs": 120 +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/node-compat-20261006-fixed.json b/docs/plans/counter-asic-3-gate/node-compat-20261006-fixed.json new file mode 100644 index 00000000..267af5dd --- /dev/null +++ b/docs/plans/counter-asic-3-gate/node-compat-20261006-fixed.json @@ -0,0 +1,102 @@ +{ + "pass": true, + "checks": { + "one_digest_on_old_and_new": true, + "new_miner_blocks_accepted": true, + "old_miner_blocks_accepted": true, + "zero_rejected_by_miners": true, + "no_wrong_version_or_reject_line": true, + "every_header_version_is_the_block_version": true, + "old_nodes_hold_the_new_blocks": true, + "counts_agree_at_the_end": true, + "clean_new_node_joined_and_synced": true, + "restarted_new_node_resynced": true, + "new_node_served_a_genesis_sync_and_survived": true, + "no_panic_in_any_node_log": true + }, + "new_binary": "vendor/igneum-node-ca3v4/target-ca3v4/release/igneumd", + "old_binary": "/Users/joshm/Projects/igneum/vendor/igneum-node/target-0314/release/igneumd", + "digests": [ + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2" + ], + "counts_after_mining": [ + { + "count": 165, + "sink": "44a4876cfc2ba782", + "daa": 165 + }, + { + "count": 165, + "sink": "44a4876cfc2ba782", + "daa": 165 + }, + { + "count": 165, + "sink": "44a4876cfc2ba782", + "daa": 165 + }, + { + "count": 165, + "sink": "44a4876cfc2ba782", + "daa": 165 + }, + { + "count": 165, + "sink": "44a4876cfc2ba782", + "daa": 165 + } + ], + "final": [ + { + "count": 195, + "sink": "0be1fa5e67bedeb1", + "daa": 195 + }, + { + "count": 195, + "sink": "0be1fa5e67bedeb1", + "daa": 195 + }, + { + "count": 195, + "sink": "0be1fa5e67bedeb1", + "daa": 195 + }, + { + "count": 195, + "sink": "0be1fa5e67bedeb1", + "daa": 195 + }, + { + "count": 195, + "sink": "0be1fa5e67bedeb1", + "daa": 195 + } + ], + "served_join_count": 165, + "serving_node_alive": true, + "accepted_old_new": [ + 120, + 75 + ], + "rejected_by_miners_old_new": [ + 0, + 0 + ], + "reject_lines": [ + [], + [], + [], + [], + [] + ], + "header_versions": { + "0": 1, + "2": 195 + }, + "secs": 160 +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/node-compat-20261006-poisoned-peer.json b/docs/plans/counter-asic-3-gate/node-compat-20261006-poisoned-peer.json new file mode 100644 index 00000000..d93951a2 --- /dev/null +++ b/docs/plans/counter-asic-3-gate/node-compat-20261006-poisoned-peer.json @@ -0,0 +1,112 @@ +{ + "pass": true, + "checks": { + "one_digest_on_old_and_new": true, + "new_miner_blocks_accepted": true, + "old_miner_blocks_accepted": true, + "zero_rejected_by_miners": true, + "no_wrong_version_or_reject_line": true, + "every_header_version_is_the_block_version": true, + "old_nodes_hold_the_new_blocks": true, + "counts_agree_at_the_end": true, + "clean_new_node_joined_and_synced": true, + "restarted_new_node_resynced": true, + "new_node_served_a_genesis_sync_and_survived": true, + "no_panic_in_any_node_log": true, + "new_node_refused_the_poisoned_blocks": true, + "poisoned_node_mined_something_to_refuse": true, + "old_hub_never_saw_a_1026_block": true + }, + "new_binary": "vendor/igneum-node-ca3v4/target-ca3v4/release/igneumd", + "old_binary": "/Users/joshm/Projects/igneum/vendor/igneum-node/target-0314/release/igneumd", + "poisoned_binary": "/private/tmp/claude-501/-Users-joshm/cd75457f-4858-4f86-9634-7481ee056b7b/scratchpad/igneumd-713ef876", + "new_node_rejects_of_1026": 12, + "poisoned_blocks_accepted_by_its_own_node": 179, + "digests": [ + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2", + "b0afb2ee93d0d6274cbf63d40ac7bed487202c5af84e035c8a634c521bffe6a2" + ], + "counts_after_mining": [ + { + "count": 147, + "sink": "9d12ad182ab77726", + "daa": 147 + }, + { + "count": 147, + "sink": "9d12ad182ab77726", + "daa": 147 + }, + { + "count": 147, + "sink": "9d12ad182ab77726", + "daa": 147 + }, + { + "count": 147, + "sink": "9d12ad182ab77726", + "daa": 147 + }, + { + "count": 147, + "sink": "9d12ad182ab77726", + "daa": 147 + } + ], + "final": [ + { + "count": 180, + "sink": "040d5ae85d45bc24", + "daa": 180 + }, + { + "count": 180, + "sink": "040d5ae85d45bc24", + "daa": 180 + }, + { + "count": 180, + "sink": "040d5ae85d45bc24", + "daa": 180 + }, + { + "count": 180, + "sink": "040d5ae85d45bc24", + "daa": 180 + }, + { + "count": 180, + "sink": "040d5ae85d45bc24", + "daa": 180 + } + ], + "served_join_count": 147, + "serving_node_alive": true, + "accepted_old_new": [ + 108, + 72 + ], + "rejected_by_miners_old_new": [ + 0, + 0 + ], + "reject_lines": [ + [], + [ + "HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting from peer 127.0.0.1:56593.", + "HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting from peer 127.0.0.1:56829.", + "HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting from peer 127.0.0.1:57104." + ], + [], + [], + [] + ], + "header_versions": { + "0": 1, + "2": 180 + }, + "secs": 160 +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/node-gates.md b/docs/plans/counter-asic-3-gate/node-gates.md index b7c62556..09901087 100644 --- a/docs/plans/counter-asic-3-gate/node-gates.md +++ b/docs/plans/counter-asic-3-gate/node-gates.md @@ -13,8 +13,14 @@ | # | Gate | Evidence required | State | |---|---|---|---| -| P1 | the rehearsal plan and the v4 override object for the rented fleet | `docs/plans/counter-asic-3-rehearsal.md`; the objects with their digests | WRITTEN (not run: the fleet agent runs it): the publish object (the live 13 fields plus the floor `N6` and the window 86,400; digest `ac8e60ce205852bdda6b554f8cbfbd9dbb040187f487cbd8affe8103633dfd56` at the worked example N6 = 219,600) and the rehearsal object (every earlier switch at 0, window 3,600, floor 14,400; digest `bc2142b178ff367ae84ff0699ff523760d8878f883375da21864ed8d3ad39237`), both read from the signalling node's start lines on throwaway private networks; `override-v4-publish-example.json` and `override-v4-rehearsal.json` in this directory | +| P1 | the rehearsal plan and the v4 override object for the rented fleet | `docs/plans/counter-asic-3-rehearsal.md`; the objects with their digests | WRITTEN (not run: the fleet agent runs it): the publish object (the live 13 fields plus the floor `N6` and the window 86,400; digest on the devnet network id `65a42ab2e63d93ac02acf761b411b31acac6c9413bbd0c6a2f4cced10509efdf` at the worked example N6 = 219,600, fork 0562a7f2), the rehearsal object (every earlier switch at 0, window 3,600, floor 14,400; digest on suffix 400 `aef46983dfd121d5ecbefd056ff02d001b89315bd0547b4e1007b0b0ee7eee2c`) and the re-cut for the hour (600-DAA epochs, window 600, floor 2,400; digest on suffix 400 `a15db4f0d6a5cc891582760ef5593e69014a5133261def84647cb7617528764f`, the value the 16 fleet boxes printed); CORRECTED 18:4xZ: the digest folds the network id in, and the first values (`ac8e60ce`, `bc2142b1`, `d23394e7`) were read on throwaway suffixes; CORRECTED 18:5xZ: the rehearsal chain inherits the devnet's genesis bits (the object sets none; the fast-time harness lowers them), so its miners are the GPU workers, not 1-thread CPU miners (the fleet's 16 CPU miners at about 80 kH/s against about 2^27 hashes a block held the height at 0 for 20 minutes); the id assertion on a worker box reads the id from the exported pack's program.h (the `program pack checked ... ` line names it), the Mac side unchanged; `override-v4-publish-example.json` and `override-v4-rehearsal.json` in this directory | | P2 | miner-signalled class activation, implemented behind the override, the fast-time gate with three cases and the failed case | `docs/plans/counter-asic-3-node.md` section 6; `infra/fast-time/class-v4-signal.mjs` | GREEN on the Mac: two of three signalling never flips (7 epochs, 6,166 to 7,583 bps), all three flips at epoch 3 (the first full window, 10,000 bps, the same line on 3 of 3 nodes, the id assertion), nobody signalling flips at the floor epoch 5 and not before; the known-failed case fails on eight checks. Rows and summary files: node doc section 6.4; `class-v4-signal-{no-flip,flip,floor,failed-case}.json` | | G6 (signalling) | the fork change on PC 2 with the igneum-pow feature | the job ids and their lines | GREEN on fork 0562a7f2 (main f39a8eb), three PC 2 jobs under one clearance ("PC 2 open for G6", 17:27Z), the mkdir lock taken 17:28:15Z and released 17:38:58Z, then 17:39:41Z to 17:55:31Z; prover on, app untouched. Job 1 `build-20261006-173017` (the six crates and the app): every build stage ok, the app tests 112 + 26 + 8, but kaspa-consensus 97 passed and 1 FAILED on 2.0's known flake `ban_is_decided_by_the_carrying_block_so_nodes_agree_on_every_voter_list` (`UnexpectedDifficulty(.., 487111630, 487128802)` in `mine_on_all`, the 0.3.10 cut's section 11 case, which also passed on the Mac and in this morning's job 155958), and cargo stopped the unit there. Treated as 2.0 did: job 2 `build-20261006-174027` (kaspa-consensus alone, 17:40 to 17:46Z): `RESULT test node [kaspa-consensus] exit 0 19 s`, `done exit 0 after 345 s ... every stage ok`. Job 3 `build-20261006-174823` (the five crates and the app, 17:48 to 17:55Z): `RESULT test node [kaspa-consensus-core igneum-exec kaspa-pow igneum-miner kaspa-p2p-flows] exit 0 35 s` and `RESULT test app/igneum-app [igneum-app] exit 0 5 s`: kaspa-consensus-core 110 + 7 (`override_params_carry_the_program_class_v4_activation ... ok`, the signal window test inside it), igneum-exec 18, kaspa-pow 15 (`program_class_signal_rule ... ok` is consensus-core's; kaspa-pow's `program_class_v4_seeds_hash_the_shadow_program_over_the_v3_day_cache ... ok`, the feature on), igneum-miner 18, kaspa-p2p-flows 33, igneum-app 112 + 26 + 8; the job's own closing line reads `failed ... upload incomplete` because the relay's blob service refused one of the nine uploads (`igneum-app.exe.zst FAILED: blob PUT: no url in the reply: service_unavailable`, the shipper's 17:25Z fault) AFTER both test stages had closed with exit 0, so the STAGE and RESULT lines are the evidence, as the shipper's warning said. Where the runs went: these three on PC 2 under the clearance given before the build-server rule arrived (18:0xZ); no further Linux build or suite is needed by this brief; the next one from this lane goes to igneum-build-1 through tools/build-remote.sh | +| P3 | the 0.3.15 digest rule (main's ruling, 20:1x UK): an absent class v4 field contributes nothing to the digest, so publish 1 (the binary) splits no handshake and publish 2 (the sixteen-field file) is the one sweep | the compat case and the refusal case on real nodes; the pinned fixtures | GREEN on fork ca3-v4-order-fix 713ef876 (with the order-test race fix 791ff22c; both handed to the shipper for release-0.3.15-node): `infra/fast-time/digest-compat.mjs` (18:58Z, suffix 995, the live thirteen-field file copied, never written): the fixed node and a 0.3.14 node (`target-0314/release/igneumd`) on the thirteen-field file print ONE digest `a89be8a7e64b7d5a...` and peer (n0 has 2 peers, the old node 1: the compat case); the fixed node on the sixteen-field file (the exec pin, the floor 219,600, the window 86,400) prints `db9a85f9ff08f72f...` and has no peer, the handshake's `Refusing peer ...: consensus params digest mismatch` line in the log (the refusal case, the rule's known-failed case: present fields move the digest). On the devnet network id the sixteen-field object digests to `1dddfa5574d5536109a5ae68dc415b55e954bd11208608440845e19670505871` (the fixed Mac binary's start line = the pinned test value), the thirteen-field live file to `b18ed271f75dd464...` (0.3.14's value), fourteen fields `23e76936...`; no file `c562d70e...` (0.3.11 to 0.3.15 unchanged). Box line on 713ef876: kaspa-consensus 98, consensus-core 111 + 7, rc 0. The rehearsal object sets both fields, so its `a15db4f0` on devnet-400 stands. The shipper's independent readings on the 0.3.15 Mac binary (release-0.3.15-node = 4c6b129d + 0562a7f2 + 2e5d3d30 + 791ff22c + 713ef876, 19:0xZ): the same three values, and at tonight's live floor 226,800 the sixteen-field object reads `2c1162e2...`, the publish-2 digest if the floor stays there (recomputed at the publish); the 0.3.14 binary exits at parse on the sixteen-field file, which is why the file follows the binary; its suite on igneum-build-1 at 713ef876: kaspa-consensus 97 + the recorded flake alone 1 of 1, consensus-core 111 + 7, igneum-exec 20, kaspa-pow 15 with both engine tests, igneum-miner 18, p2p-flows 33. Summary `digest-compat-20261006.json` | + +| P4 | the two 0.3.15 node faults found on the live devnet (the canary's version 1026, the pruned hub's sync panic), fixed in the same fork tree | the unit tests; the node-cut compat gate on real nodes (a MINING new node beside a 0.3.14 node on the live object, the old node accepting the new node's blocks; a clean new node joining through the old hub; a clean OLD node joining through the NEW node, which must survive serving a sync from the genesis; the new node restarted and re-synced; every header version the block version; no panic); the ledger rows | GREEN on fork ca3-v4-order-fix 7961c5f1 (= 791ff22c + 713ef876 + 17c60367 the signal gate + 7961c5f1 the retention error; the shipper's release-0.3.15-node). The gate `infra/fast-time/node-compat.mjs` (20:15:5x to 20:19:04Z, suffix 994, the live object plus CPU genesis bits, the 0.3.14 binary `target-0314/release/igneumd` as the old side): SUMMARY PASS: one digest `b0afb2ee93d0d627` on all five nodes; accepted old/new 120/75, rejected 0/0; header versions {0: the genesis, 2: 195}; counts 195/195/195/195/195 at the end (the clean new node through the old hub, the clean old node served from the genesis by the new node, which stayed alive, and the restarted new node all at 195); no `wrong block version`, reject or panic line in any log. The known-failed case on the canary binary 713ef876 beside 0.3.14 (20:00 to 20:02Z): SUMMARY FAIL, `HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting` on the old nodes, `P2P, got reject message: wrong block version` on the new, counts 138 against the new node's 101, the restarted new node never re-synced (the fleet's p2-3090-1). Unit tests: `block_template_uses_current_block_version` (2 on the thirteen-field state, 1026 with both v4 fields), `program_class_signal_rule`, `a_sync_request_below_retention_is_an_error_not_a_panic`; the box on 7961c5f1: kaspa-consensus 99, consensus-core 111 + 7, rc 0 (the shipper's six-crate line: exec 20, miner 18, pow 15, p2p-flows 33). Ledger rows N1 and N2 in `docs/fud-ledger.md`. RECEIVE SIDE (the clean canary, 21:3x UK: a 7961c5f1 node accepted and relayed a version-1026 block off a poisoned peer and was disconnected by every 0.3.14 peer): fork f1ea7a38 gates the header version RULE like the stamp (signalling inactive: the version must be exactly 2, 0.3.14's rule; active: the low byte); test inside `cheap_checks_run_before_the_pow_engine` (a 1026 header refused with `WrongBlockVersion(1026, 2)` before the engine on the old-format network, accepted with both fields installed); box on f1ea7a38: kaspa-consensus 99, consensus-core 111 + 7, rc 0. The gate with a POISONED peer (`--poisoned <713ef876 igneumd>` mining into the new node, 20:42:5x to 20:45:42Z): SUMMARY PASS: the new node refused the poisoned blocks 12 times (`HandleRelayInvsFlow flow error: wrong block version: got 1026 but expected 2, disconnecting`), the old hub's log carries no reject and no 1026 block (header versions {0: the genesis, 2: 180}), the new node stayed the hub's peer and mined 72 accepted blocks, counts 180 on every clean node including the clean joins and the restart. The stale blocks on the live devnet: a 0.3.14 node holding 1026 blocks is disconnected by its 0.3.14 peers by THEIR rule when it relays them, which no new-node change alters; new nodes are immune from f1ea7a38; the fleet wipes the poisoned datadirs. Owed: a truly pruned serving node in a harness (hours of fast-time chain at pruning depth 13,838). Summaries `node-compat-20261006-fixed.json`, `node-compat-20261006-canary-failed-case.json`, `node-compat-20261006-poisoned-peer.json` | + +| P5 | the dependency pass for 0.3.15.1 (the night battery's audit, mirror branch fbb72e5: the fork's lock file) | `cargo audit` before and after on the box; the six-crate suite | DONE on fork `ca3-v4-deps` f8f0f1df (from 7961c5f1): `cargo audit` on igneum-build-1 before: 6 vulnerabilities (crossbeam-epoch RUSTSEC-2026-0204, h2 2026-0258, quinn-proto 2026-0185, ruint 2026-0220, rustls 2026-0285, tracing-subscriber 2025-0055; the battery's 22 counts advisory paths), 16 warnings (unmaintained, unsound, yanked); the three peer-reachable crates first (h2 0.4.6 to 0.4.20, quinn-proto 0.11.14 to 0.11.19, rustls 0.23.39 to 0.23.45 with rustls-webpki 0.103.15), then crossbeam-epoch 0.9.21 and ruint 1.20.1 (rand_pcg added), every one a patch or minor bump by the audit's own solution line; after: 1 vulnerability, 16 warnings; the one left is tracing-subscriber 0.2.25, pinned by ark-relations 0.5.1 through ark-groth16 and ark-snark (a 0.2 to 0.3 major bump outside the fork's pins, which name 0.3 in bridge/Cargo.toml), named here, not taken. The six-crate suite on the box on the new lock: kaspa-consensus 99, consensus-core 111 + 7, igneum-exec 20, kaspa-pow 15, igneum-miner 18, kaspa-p2p-flows 33, rc 0 (80 s). The 16 warnings are unmaintained or unsound transitive crates (async-std, atty, bincode 1, derivative, instant, mach, paste, proc-macro-error, rustls-pemfile, anyhow, event-listener, faster-hex, lru, chacha20, spin), none with a bump to take, each a dependency choice for 0.3.16 | + Summary files in this directory: `class-v4-20261006-1553Z-cpu.json` (G4 run 1), `class-v4-20261006-never-failed-case.json` (G4 run 2, the failed case), `class-v4-20261006-metal.json` (G4b), `class-v4-20261006-id-rerun.json` (G4 on the program-id fix, with the id assertion), `class-v4-20261006-id-failed-case.json` (the id assertion's failed case). diff --git a/docs/plans/counter-asic-3-public-text-2026-10-07.md b/docs/plans/counter-asic-3-public-text-2026-10-07.md new file mode 100644 index 00000000..ba75180b --- /dev/null +++ b/docs/plans/counter-asic-3-public-text-2026-10-07.md @@ -0,0 +1,33 @@ +# The chip claim, public text (7 October 2026; REWRITTEN LAUNCH-FIRST 18:3x UK on the project lead's "I thought we were making it 2.1 from launch?": the testnet and mainnet objects set program_class_v4_activation_daa to 0, so class v4 is live from genesis and the launch number is 2.1x to 3.9x on day one; the 5x to 9x is the class v3 baseline the work started from, stated only as that; the devnet's own activation height is a devnet fact only. Served since 11:03 UK on master 9162c847 with main's two cuts: no mention of the disclosure prize until the publish word, and row 17 in evidence.md's eight-column shape) + +Three texts and one ledger row, written by the Counter ASIC lane, which owns the chip model. Every number carries its label: measured (a card or a chain we ran, with the date), modelled (arithmetic on cited parts), claimed (a vendor's figure, never measured by us), designed (a rule in a class, not yet measured). Sources: `docs/analysis/chip-model-v3.md` sections 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` and `f4-weakday.md` (branch attack-pass), `docs/design/class-v5-stored-state.md`, the datacentre and market-cap rows of 7 October (lanes 3 and the fleet), the cryptanalysis plan in `docs/plans/funding.md`. + +## 1. The home page's chip line (replaces the hero sentence served since 6 October 16:21Z) + +Built for graphics cards. At launch the strongest chip in our public model reaches 2.1x to 3.9x per joule against an RTX 5090, under class v4 from the first block. Class v5 then makes the dataset the chain's own state, so a chip that stores it or recomputes it is wrong on every item. Without class v4 the same chip would reach 5x to 9x. The model and every measurement are public. + +## 2. The litepaper's chip section (replaces the paragraph that begins "The chip model: 5x to 9x per joule") + +The chip model. We price the strongest chip we can design against an RTX 5090 and publish the arithmetic. Class v4 is live from the first block on the testnet and the mainnet (the ladder's rung 0 at genesis), so the launch number is the class v4 row. The honest card: an RTX 5090 mines class v3 at 136 MH/s on 350 W in the bench and 290 W in the app (measured, 6 October 2026); an Apple M5 Max at 27 MH/s on 21 W (measured, 6 October 2026); an H100 SXM at 249 MH/s, 98 percent of its random-read ceiling like the 5090, 1.78x the 5090's hash at 1.15x the tuned 5090's hash per watt and a third of the hash per rented dollar (measured, 7 October 2026), so datacentre silicon does not change the chip question. The CPU verifier takes 2.33 ms per warp of 32 hashes on one M5 Max core under class v4 (measured, 6 October 2026), against a gate of 10 ms. + +| The chip and the class | Edge over an RTX 5090 per joule | Label and date | +|---|---|---| +| At launch: a memory-controller chip that stores the whole dataset, under class v4 (about 100,000 integer ops per hash in the latency shadow, so the chip carries a GPU-class datapath beside its memory) | 2.1x with a core as costly per op as the GPU's (k = 1); 3.9x with the core Bitmain claimed for its Antminer X9 (k about 0.33), a product withdrawn before any unit shipped | modelled on measured card watts, 6 October 2026; the X9 figure claimed, never measured | +| The same chip at the ladder's second rung (about 200,000 ops per hash), reached by miner signal | about 2.8x | modelled, 7 October 2026 | +| Any chip under class v5, where the dataset is the chain's own state | a stateless or stale chip is wrong on every item, so the stored-dataset chip and the recompute chip are removed as categories; the verifier pays 0.2 ms more per warp | designed, 7 October 2026 | +| A chip caching the hottest 0.1 percent of items (about 1 MB of SRAM) | bounded at 1.067x at the ceiling, 1.005x on about half the hours and 1.048x on 5 percent | measured census of 1,024 programs, 7 October 2026; the source rule in the next class | +| A per-day FPGA that recomputes the dataset with cheap multipliers on a weak day | at most 12 percent more hash rate on 12 days a century, nothing on the other days and nothing for any chip | measured census of 2^24 days, 7 October 2026; the rule in the next class | +| When a stored-dataset chip pays for itself | at about USD 100 M of market cap in the first two years, not before | modelled, 7 October 2026 | +| The baseline the work started from: the same chip under class v3, without the shadow (the Ethash class) | 5x to 9x (5.1x on GDDR7, 9.2x on eight HBM3 stacks; the Ethash chips of this class reached 2.1x to 4.8x) | modelled, 6 October 2026; the precedent measured by others, 2020 to 2022; never the launch state | + +What a miner sees from this. Class v4 costs a 5090 about 80 W more for 0.2 percent of rate, an M5 Max 16 W more for 1.5 percent, an RX 9070 XT and an RTX 4070 nothing (all measured, 6 October 2026). The ladder that sets how much work rides in the shadow starts at rung 0 at genesis and climbs by miner signal; its third rung is inadmissible today because a server core verifies it in 10.85 ms, over the gate (measured, 7 October 2026). On the devnet, which started on class v3, class v4 arrives by miner signal at a published height (a devnet fact, not a launch one). The next test of the model is not ours: the cryptanalysis plan buys three external lots against the mixer, the chained cache and the acceptance rule. + +## 3. The miner page's line + +Your card against the strongest chip we can price: an RTX 5090 at 136 MH/s on 350 W (measured 6 October 2026); at launch the chip reaches 2.1x to 3.9x per joule under class v4 (modelled on measured watts), and under class v5 it is wrong on every item because the dataset is the chain's own state (designed). Without class v4 it would be 5x to 9x. The model and the measurements are public. + +## 4. The ledger row (docs/evidence.md row 17, in the table's eight columns as served) + +| # | Claim | Where it is made | Status | Version or commit | Reproducible test | Result, date, machine | Independent verification | +|---|---|---|---|---|---|---|---| +| 17 | The chip resistance claim: at launch the strongest chip in the public model reaches 2.1x (k = 1) to 3.9x (k about 0.33) per joule against an RTX 5090 under class v4, live from genesis on the testnet and the mainnet; the ladder's second rung brings it to about 2.8x; class v5 makes the dataset the chain's state so a stateless or stale chip is wrong on every item; the hot-set cache is bounded at 1.067x at the ceiling and the weak-day FPGA at 12 percent on 12 days a century, both routed to the next class; datacentre silicon does not change the question; a stored-dataset chip pays for itself only at about USD 100 M of market cap in two years; without class v4 the same chip would reach 5x to 9x (the class v3 baseline, the devnet's starting state, never the launch state) | the home page's chip line, the litepaper's chip section (/litepaper#chip-model), the miner page's line | tested by the team (every card, the verifier, the two attack-pass bounds, 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/analysis/ca3-v4-uniform.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's arithmetic in its file; the card rows by the benchmark package; the attack-pass harnesses `tools/attack/f8-uniform` and the F4 census; the verifier by `igneum-pow bench` | 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; 2.1x, 3.9x, 2.8x at launch; 1.067x at the ceiling; 12 percent on 12 days a century; 10.85 ms at rung 3; USD 100 M; 5.1x to 9.2x the class v3 baseline; 6 and 7 October 2026, the M5 Max, PC 2's RTX 5090, PC 1's RX 9070 XT and RTX 4070, a rented H100 SXM, igneum-build-1 | none yet; the three cryptanalysis lots are the next test | diff --git a/docs/plans/counter-asic-3-rehearsal.md b/docs/plans/counter-asic-3-rehearsal.md index c652aa14..dc4034ff 100644 --- a/docs/plans/counter-asic-3-rehearsal.md +++ b/docs/plans/counter-asic-3-rehearsal.md @@ -16,7 +16,7 @@ The class v4 activation on a chain of real boxes, in the shape the live devnet w ## 2. The override objects -Both objects below are JSON text to be written verbatim; a `never` height is `18446744073709551615`, which no JSON tool that goes through a double may rewrite (the fast-time harness's rule). The digests are what a node of the signalling commit prints at start (`Consensus params digest`); the fleet agent compares every box's line against them and the stale box's against its own. +Both objects below are JSON text to be written verbatim; a `never` height is `18446744073709551615`, which no JSON tool that goes through a double may rewrite (the fast-time harness's rule). The digests are what a node of the signalling commit (fork 0562a7f2) prints at start (`Consensus params digest`); the fleet agent compares every box's line against them and the stale box's against its own. CORRECTION 18:4xZ (the rehearsal's first reading): the digest folds the NETWORK ID in, so one file prints another digest on another `--devnet-suffix` (the same binary and file: `a15db4f0...` at suffix 400, `d23394e7...` at 996, `45eeea99...` at 401); the first values written here were read on throwaway suffixes and were wrong for the fleet's suffix 400. Every digest below now names its network id; the one the live devnet prints is the one read with no suffix. ### 2a. The live devnet publish object (NOT published by this plan; the shape the cut will use) @@ -26,7 +26,7 @@ The live file today (`/tmp/igneum-devnet/override-v3.json`, 13 fields, read 16:5 {"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":"bb45cf0dd2d7cc97ebfa5a2701527c09a8ede5d32de74efead9caa293b15688a","exec_restart_trust_daa":200000,"program_class_v4_activation_daa":N6,"program_class_v4_signal_window_daa":86400} ``` -What the two fields do on the live devnet: every node of the signalling binary stamps object byte 4 into its templates from its first block, so the signal share climbs as the fleet updates; the class flips at the first epoch boundary whose window (the 86,400 DAA, one day, below that epoch's seed block) has 95 percent of its blue blocks signalling, which is about a day after the LAST box of 95 percent of the hash rate has updated; the floor `N6` flips it regardless at the latest. Digest of this object: `ac8e60ce205852bdda6b554f8cbfbd9dbb040187f487cbd8affe8103633dfd56` (read from the signalling node's start line, 18:05Z; the node also prints `Program class v4 from the override file: active from epoch 61 (DAA score 219600 rounded up to the epoch boundary at 219600, epochs of 3600 DAA)` and the window line) (with `N6` = 219,600 as the worked example; any other `N6` moves it). +What the two fields do on the live devnet: every node of the signalling binary stamps object byte 4 into its templates from its first block, so the signal share climbs as the fleet updates; the class flips at the first epoch boundary whose window (the 86,400 DAA, one day, below that epoch's seed block) has 95 percent of its blue blocks signalling, which is about a day after the LAST box of 95 percent of the hash rate has updated; the floor `N6` flips it regardless at the latest. Digest of this object on the DEVNET network id (no suffix), the signalling fork 0562a7f2: `65a42ab2e63d93ac02acf761b411b31acac6c9413bbd0c6a2f4cced10509efdf` (18:4xZ; the earlier `ac8e60ce...` was read at suffix 998 and does not apply); the no-file devnet digest on the same binary is `7f2e49be...`, the fork test's pinned value. The 0.3.15 binary may move both if 0.3.14 added a digest field: the shipper reads the live value from the 0.3.15 node. The node also prints `Program class v4 from the override file: active from epoch 61 (DAA score 219600 rounded up to the epoch boundary at 219600, epochs of 3600 DAA)` and the window line. CUT ORDER (found by the shipper, 18:3xZ): a 0.3.13 node refuses the 15-field file at PARSE (`unknown field program_class_v4_activation_daa`) and exits before any handshake, so the file reaches a node only with or after its 0.3.15 binary (the app applies the manifest's override after the update; the hand nodes and the seeds get the file in the same step as the binary, never before) (with `N6` = 219,600 as the worked example; any other `N6` moves it). ### 2b. The rehearsal object (the fleet chain) @@ -36,11 +36,11 @@ A fresh chain, every earlier switch at 0 (the testnet's shape: the chain is born {"difficulty_v2_activation_daa":0,"proving_v0_activation_daa":0,"fees_v1_activation_daa":0,"finality_v3_activation_daa":0,"program_class_v3_activation_daa":0,"proving_v1_activation_daa":0,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":0,"exec_restart_number":18446744073709551615,"exec_restart_hash":"","exec_restart_trust_daa":18446744073709551615,"program_class_v4_activation_daa":14400,"program_class_v4_signal_window_daa":3600} ``` -The arithmetic: epochs of 3,600 DAA, lead 600. Epoch `e`'s seed block is the last chain block below `3600 e - 600`; its window is full when that block's DAA is at least 3,600: epoch 1's seed block sits at DAA 2,999 (not full), epoch 2's at 6,599 (full). With every mining box signalling 4, the tally at epoch 2's seed block is 100 percent of the blue blocks in DAA 2,999 to 6,599, so the class flips at epoch 2, DAA 7,200, about 2 hours after genesis; the floor (epoch 4, DAA 14,400) is 2 hours later and is not reached by the run. Digest: `bc2142b178ff367ae84ff0699ff523760d8878f883375da21864ed8d3ad39237` (the signalling node's start line on this object, 18:05Z, with `Program class v4 from the override file: active from epoch 4 (DAA score 14400 ...)` and `Program class v4 signal window from the override file: 3600 DAA ...`); the devnet digest with no file at all is `7f2e49beabc253f327c5ac6bb457a674ea7f527af2971c95d3bdf65ef8bcf977` on this binary (the fork's pinned test), `c562d70e...` on 0.3.11 to 0.3.13. +The arithmetic: epochs of 3,600 DAA, lead 600. Epoch `e`'s seed block is the last chain block below `3600 e - 600`; its window is full when that block's DAA is at least 3,600: epoch 1's seed block sits at DAA 2,999 (not full), epoch 2's at 6,599 (full). With every mining box signalling 4, the tally at epoch 2's seed block is 100 percent of the blue blocks in DAA 2,999 to 6,599, so the class flips at epoch 2, DAA 7,200, about 2 hours after genesis; the floor (epoch 4, DAA 14,400) is 2 hours later and is not reached by the run. Digest on `igneum-devnet-400` (suffix 400): `aef46983dfd121d5ecbefd056ff02d001b89315bd0547b4e1007b0b0ee7eee2c` (the signalling node's start line, 18:4xZ; the earlier `bc2142b1...` was read at suffix 997); the node also prints `Program class v4 from the override file: active from epoch 4 (DAA score 14400 ...)` and `Program class v4 signal window from the override file: 3600 DAA ...`. ### 2c. The re-cut for the hour (18:1x UTC, the coordinator's "run it now") -If the fleet chain runs the devnet's 1 block/s, object 2b flips at 2 hours; this object flips at 20 minutes: epochs of 600 DAA (lead 100), window 600, floor 2,400. Epoch `e`'s seed block sits at DAA `600 e - 100`; the window is full from epoch 2 (seed at 1,100), so the flip is at epoch 2, DAA 1,200, minute 20 from the chain's start; the floor is epoch 4, DAA 2,400, minute 40; the run ends at DAA 1,800 (minute 30, one epoch after the flip; the floor is not reached). If the fleet chain is made to run 10 blocks/s instead, object 2b itself gives 6 / 12 / 24 minutes (window / flip / floor) and this one 1 / 2 / 4 minutes, too fast for a 15-minute sample cadence: use 2b. File `docs/plans/counter-asic-3-gate/override-v4-rehearsal-1bps.json`, digest `d23394e796fb542274207f1d281bc40126040e04f86882faa0b49ea3c3f1f91b` (the signalling node's start line, with `PoW schedule from the override file: epoch 600 DAA, lead 100 DAA, day 86400000 ms`, `Program class v4 from the override file: active from epoch 4 (DAA score 2400 ...)`, `Program class v4 signal window from the override file: 600 DAA ...`). +If the fleet chain runs the devnet's 1 block/s, object 2b flips at 2 hours; this object flips at 20 minutes: epochs of 600 DAA (lead 100), window 600, floor 2,400. Epoch `e`'s seed block sits at DAA `600 e - 100`; the window is full from epoch 2 (seed at 1,100), so the flip is at epoch 2, DAA 1,200, minute 20 from the chain's start; the floor is epoch 4, DAA 2,400, minute 40; the run ends at DAA 1,800 (minute 30, one epoch after the flip; the floor is not reached). If the fleet chain is made to run 10 blocks/s instead, object 2b itself gives 6 / 12 / 24 minutes (window / flip / floor) and this one 1 / 2 / 4 minutes, too fast for a 15-minute sample cadence: use 2b. File `docs/plans/counter-asic-3-gate/override-v4-rehearsal-1bps.json`, digest on `igneum-devnet-400` (suffix 400): `a15db4f0d6a5cc891582760ef5593e69014a5133261def84647cb7617528764f`, the value all 16 fleet boxes printed on the Linux build of the same fork commit 0562a7f2 (PC 2 job build-20261006-174823; the earlier `d23394e7...` was this file at suffix 996), with `PoW schedule from the override file: epoch 600 DAA, lead 100 DAA, day 86400000 ms`, `Program class v4 from the override file: active from epoch 4 (DAA score 2400 ...)`, `Program class v4 signal window from the override file: 600 DAA ...`). ``` {"difficulty_v2_activation_daa":0,"proving_v0_activation_daa":0,"fees_v1_activation_daa":0,"finality_v3_activation_daa":0,"program_class_v3_activation_daa":0,"proving_v1_activation_daa":0,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":0,"exec_restart_number":18446744073709551615,"exec_restart_hash":"","exec_restart_trust_daa":18446744073709551615,"pow_epoch_blocks":600,"pow_epoch_lead":100,"program_class_v4_activation_daa":2400,"program_class_v4_signal_window_daa":600} @@ -55,12 +55,12 @@ With this object the step-7 line reads `epoch 2 (share 10000 bps over 600 DAA .. | 1 | Fetch the signalling commit's Linux binaries from the G6 build job (the shas in node-gates.md), verify every sha256, place `igneumd` and `igneum-miner` on every box; the 0.3.13 Linux `igneumd` on the stale box | every sha matches | | 2 | Write the rehearsal object (2b) as `override.json` on every box, byte for byte (sha256 the file on each box and compare) | one sha on every box | | 3 | Start the seed box: `igneumd --devnet --devnet-suffix=400 --nodnsseed --disable-upnp --listen=0.0.0.0:16411 --rpclisten=127.0.0.1:16410 --rpclisten-json=127.0.0.1:16412 --override-params-file=override.json --utxoindex --enable-unsynced-mining --yes --appdir=` (ports of the box's choosing, never the live devnet's 26610/26611); read its first lines: `Consensus params digest` equals 2b's, `Program class v4 from the override file: active from epoch 4 (DAA score 14400 ...)`, `Program class v4 signal window from the override file: 3600 DAA ...` | the three lines | -| 4 | Start every mining box the same way with `--connect=:16411`, then its miner: `igneum-miner mine grpc://127.0.0.1:16410 1 100000000