// node --test tools/observer/detector.test.mjs // The detector is trusted only after it fires on one known-failed case (a fabricated population that is one fixed // design) and stays quiet on one known-finished case (a fabricated honest population shaped like the devnet's, plus // the live devnet in `--dry` mode, README "Detector"). Fixtures are deterministic (SplitMix64). import { test } from 'node:test'; import assert from 'node:assert/strict'; import { aggregate, analyse, cardBands, calcWork, targetFromBits, workDouble, chi2Uniform, cliques, modelOf, DEFAULTS } from './detector.mjs'; // ---------- fixtures ---------- function rng(seed) { let s = BigInt(seed); return () => { s = (s + 0x9e3779b97f4a7c15n) & 0xffffffffffffffffn; let z = s; z = ((z ^ (z >> 30n)) * 0xbf58476d1ce4e5b9n) & 0xffffffffffffffffn; z = ((z ^ (z >> 27n)) * 0x94d049bb133111ebn) & 0xffffffffffffffffn; z ^= z >> 31n; return z; }; } const unit = next => Number(next() >> 11n) / 2 ** 53; function poisson(next, lambda) { if (lambda > 50) { const u1 = unit(next), u2 = unit(next); return Math.max(0, Math.round(lambda + Math.sqrt(lambda) * Math.sqrt(-2 * Math.log(u1 || 1e-12)) * Math.cos(2 * Math.PI * u2))); } let L = Math.exp(-lambda), k = 0, p = 1; do { k++; p *= unit(next); } while (p > L); return k - 1; } const BITS = 487714602; // a live devnet value (6 October 2026); work about 2.4e8 hashes per block const WORK = workDouble(BITS); const L = DEFAULTS.epochLen; // miners: [{id, mhs: base rate, program: [per-epoch multipliers] | null, nonce: 'random' | 'counter', lateStart?: bool}] // Epoch e (0-based) spans DAA [e L, (e + 1) L) and 3,600 s of wall time; blocks per miner per epoch ~ Poisson(rate x secs / work) function population(miners, epochs, seed = 1) { const next = rng(seed); const rows = []; const counters = new Map(); for (let e = 0; e < epochs; e++) { const t0 = 1_791_000_000_000 + e * L * 1000; for (const m of miners) { const mult = m.program ? m.program[e % m.program.length] : 1; const lambda = m.mhs * 1e6 * L / WORK * mult; const n = poisson(next, lambda); for (let i = 0; i < n; i++) { let frac = unit(next); if (m.lateStart && frac < 0.15) frac = 0.15 + unit(next) * 0.85; const daa = e * L + Math.floor(frac * L); let nonce; if (m.nonce === 'counter') { const c = (counters.get(m.id) || 0n) + BigInt(1 + Math.floor(unit(next) * 50_000)); counters.set(m.id, c); nonce = c; } else nonce = next(); rows.push({ vote_key_hash: m.id + 'f'.repeat(56), daa_score: daa, timestamp_ms: t0 + Math.floor(frac * L * 1000), color: unit(next) < 0.97 ? 'blue' : 'red', bits: BITS, nonce: nonce.toString() }); } } } // the open epoch after the last closed one, a few blocks, so the window is the closed epochs rows.push({ vote_key_hash: miners[0].id + 'f'.repeat(56), daa_score: epochs * L + 5, timestamp_ms: 1_791_000_000_000 + epochs * L * 1000 + 5000, color: 'pending', bits: BITS, nonce: '1' }); return rows; } const HONEST = [ { id: 'aaaa0001', mhs: 50, nonce: 'random' }, { id: 'aaaa0002', mhs: 50, nonce: 'random' }, { id: 'bbbb0001', mhs: 25, nonce: 'random' }, { id: 'cccc0001', mhs: 1.6, nonce: 'random' }, { id: 'dddd0001', mhs: 13, nonce: 'random' }, { id: 'dddd0002', mhs: 13, nonce: 'random' }, { id: 'dddd0003', mhs: 13, nonce: 'random' }, ]; // one fixed design under three ids: the same per-program response (a compute-bound sequencer whose rate follows the // program's op mix, +-30%), counting nonces upward from 0, and compiled per program (no blocks in the first 15% of an epoch) const DESIGN_PROGRAM = [1.0, 1.3, 0.8, 1.2, 0.9, 1.25, 1.1, 0.85, 1.3, 0.95, 1.15, 0.8]; const DESIGN = [ { id: 'ee000001', mhs: 40, program: DESIGN_PROGRAM, nonce: 'counter', lateStart: true }, { id: 'ee000002', mhs: 40, program: DESIGN_PROGRAM, nonce: 'counter', lateStart: true }, { id: 'ee000003', mhs: 40, program: DESIGN_PROGRAM, nonce: 'counter', lateStart: true }, ]; const BANDS = [{ model: '5090', n: 100, p5: 101, p50: 115, p95: 125 }, { model: 'M5 Max', n: 100, p5: 20.4, p50: 23.4, p95: 27.7 }, { model: '9070 XT', n: 50, p5: 16.8, p50: 17, p95: 19.1 }, { model: 'Intel UHD', n: 100, p5: 1.8, p50: 2.2, p95: 2.4 }]; // Run the detector epoch by epoch as the observer would (one analyse per closed epoch, prev carried), return the last function runWindows(rows, opts, bands, fromEpoch, toEpoch) { let prev = null, out = null; for (let e = fromEpoch; e <= toEpoch; e++) { out = analyse(aggregate(rows, opts), opts, bands, prev, e * L + 5); prev = out.state; } return out; } // ---------- the work rule ---------- test('calcWork and targetFromBits follow the chain rule', () => { // Bitcoin's genesis bits: target 0x00ffff << 8 * (0x1d - 3) assert.equal(targetFromBits(0x1d00ffff), 0xffffn << BigInt(8 * 26)); const w = calcWork(0x1d00ffff); assert.equal(w, ((1n << 256n) - 1n - targetFromBits(0x1d00ffff)) / (targetFromBits(0x1d00ffff) + 1n) + 1n); // the double form (2^256 / target) agrees with the floored BigInt form to 1e-8 on a live value (ratio 1 + 3e-9) assert.ok(Math.abs(workDouble(BITS) / Number(calcWork(BITS)) - 1) < 1e-8); assert.ok(Math.abs(chi2Uniform([10, 10, 10, 10]) - 0) < 1e-12); }); // ---------- the known-finished case: an honest population stays quiet ---------- test('an honest population of 7 ids over 12 epochs raises no flag, no clique and no alert', () => { const rows = population(HONEST, 12, 7); const out = runWindows(rows, {}, BANDS, 6, 12); const s = out.state; assert.equal(s.window.epochs.length, 6); for (const m of s.miners) assert.deepEqual(m.flags, [], `${m.id} flagged ${m.flags}`); assert.equal(s.correlation.groups.length, 0, JSON.stringify(s.correlation)); assert.equal(s.alert.active, false); assert.equal(s.alert.held, 0); // bands: the 50 MH/s ids match 5090/2, the 25 matches M5 Max/1, the 13s match 5090/8 assert.ok(s.miners.find(m => m.id === 'aaaa0001').band.matches.includes('5090/2')); assert.ok(s.miners.find(m => m.id === 'bbbb0001').band.matches.includes('M5 Max/1')); assert.ok(s.miners.find(m => m.id === 'dddd0001').band.matches.includes('5090/8')); // early share near a tenth, nonces uniform, spreads at Poisson for (const m of s.miners) { assert.ok(m.early_share_pct > 6 && m.early_share_pct < 14, `${m.id} early ${m.early_share_pct}`); assert.ok(m.nonce.chi2_low4 < DEFAULTS.chi2Crit && m.nonce.chi2_high4 < DEFAULTS.chi2Crit); if (m.excess_spread_pct !== null) assert.ok(m.excess_spread_pct < 10, `${m.id} excess ${m.excess_spread_pct}`); } }); // ---------- the known-failed case: one fixed design under three ids fires ---------- test('a fixed design under 3 ids beside 7 honest ids is flagged, forms a clique and alerts after the hold', () => { const rows = population([...HONEST, ...DESIGN], 13, 11); const opts = {}; // window after the first 6 closed epochs: per-id flags and the candidate, held 1 let out = runWindows(rows, opts, BANDS, 6, 6); const s1 = out.state; for (const id of ['ee000001', 'ee000002', 'ee000003']) { const m = s1.miners.find(x => x.id === id); assert.ok(m.flags.includes('nonce'), `${id} nonce flag: ${JSON.stringify(m.nonce)}`); assert.ok(m.flags.includes('late_start'), `${id} late start: ${m.early_share_pct}%`); assert.ok(m.flags.includes('spread'), `${id} spread: excess ${m.excess_spread_pct}% steady ${m.steady}`); assert.ok(m.band && !m.band.matches.includes('5090/1')); } for (const m of s1.miners.filter(x => x.id.startsWith('aaaa') || x.id.startsWith('bbbb') || x.id.startsWith('cccc') || x.id.startsWith('dddd'))) assert.deepEqual(m.flags, [], `${m.id} flagged ${m.flags}`); const g = s1.correlation.groups.find(x => x.kind === 'design_candidate'); assert.ok(g, 'a design candidate clique: ' + JSON.stringify(s1.correlation)); assert.deepEqual([...g.ids].sort(), ['ee000001', 'ee000002', 'ee000003']); assert.ok(g.min_r > DEFAULTS.r); assert.equal(s1.alert.held, 1); assert.equal(s1.alert.active, false); assert.ok(out.events.some(t => t.includes('move as one machine'))); // the hold: six windows later the alert is active, with the ALERT event once out = runWindows(rows, opts, BANDS, 6, 11); assert.equal(out.state.alert.held, 6); assert.equal(out.state.alert.active, true); assert.ok(out.events.some(t => t.startsWith('Detector ALERT')), out.events.join(' | ')); const again = analyse(aggregate(rows, opts), opts, BANDS, out.state, 12 * L + 5); assert.equal(again.state.alert.active, true); assert.ok(!again.events.some(t => t.startsWith('Detector ALERT')), 'the ALERT event is not repeated'); }); test('three ids of one honest machine that pause together are a machine_group, not a design candidate', () => { // one card under 3 ids whose rate halves for two epochs (a shared machine): correlated, no design flag const shared = [0, 1, 2].map(i => ({ id: `cc00000${i}`, mhs: 13, program: [1, 1, 0.55, 0.55, 1, 1, 1, 1, 1, 1, 1, 1], nonce: 'random' })); const rows = population([...HONEST, ...shared], 12, 5); const s = runWindows(rows, {}, BANDS, 6, 12).state; for (const m of s.miners.filter(x => x.id.startsWith('cc0'))) assert.deepEqual(m.flags, [], `${m.id} ${m.flags}`); assert.equal(s.alert.active, false); assert.equal(s.alert.held, 0); assert.ok(s.correlation.groups.every(g => g.kind === 'machine_group'), JSON.stringify(s.correlation.groups)); }); test('a window shorter than minEpochsCorr correlates nothing and a lone flagged id is no alert', () => { const rows = population([...HONEST, DESIGN[0]], 4, 3); const s = runWindows(rows, {}, BANDS, 3, 4).state; assert.equal(s.correlation.pairs_tested, 0); assert.equal(s.alert.held, 0); assert.ok(s.miners.find(m => m.id === 'ee000001').flags.includes('nonce')); }); // ---------- card bands from the log intake ---------- test('cardBands maps worker labels to the GPUs line and reads the STATUS rate', () => { const rows = [ { label: 'win-ae432dc7', line: '1791270059 GPUs: NVIDIA GeForce RTX 5090 (CUDA); AMD Radeon(TM) Graphics (OpenCL, gfx1036); AMD Radeon RX 9070 XT (OpenCL, gfx1201)' }, { label: 'miner-nvidia-ae432dc7-1', line: "1791270351.028 STATUS 'nvidia-ae432dc7-1' [worker]: 181s jobs=1049 accepted=73 hash=97.40 MH/s wall (123.78 MH/s inside jobs) now=123.73 MH/s wall (123.79 MH/s inside jobs, 222 jobs) identities=8" }, { label: 'miner-nvidia-ae432dc7-1', line: "1791270361.028 STATUS 'nvidia-ae432dc7-1' [worker]: 191s jobs=1049 accepted=73 hash=97.40 MH/s wall (123.78 MH/s inside jobs) now=120.10 MH/s wall (123.79 MH/s inside jobs, 222 jobs) identities=8" }, { label: 'miner-amd-ae432dc7-3', line: "1791270447.809 STATUS 'amd-ae432dc7-3' [worker]: 271s jobs=2457 accepted=23 hash=19.03 MH/s wall (19.10 MH/s inside jobs) now=19.09 MH/s wall (19.09 MH/s inside jobs, 274 jobs) identities=8" }, { label: 'miner-amd-ae432dc7-3', line: "1791270000.809 STATUS 'amd-ae432dc7-3' [worker]: 30s jobs=2 accepted=0 hash=1.00 MH/s wall (1.00 MH/s inside jobs) now=5.00 MH/s wall (5.00 MH/s inside jobs, 2 jobs) identities=8" }, { label: 'miner-other-37ba0461-1', line: "1791269654.125 STATUS 'other-37ba0461-1' [worker]: 21057s jobs=18608 accepted=268 hash=1.85 MH/s wall (1.85 MH/s inside jobs) now=1.84 MH/s wall (1.84 MH/s inside jobs, 27 jobs) identities=1" }, { label: 'win-37ba0461', line: '1791248508 GPUs: Intel(R) UHD Graphics (OpenCL)' }, ]; const b = cardBands(rows); const by = Object.fromEntries(b.map(x => [x.model, x])); assert.equal(by['5090'].n, 2); assert.equal(by['5090'].p95, 123.73); assert.equal(by['9070 XT'].n, 1, 'the 30-s warm-up reading is dropped'); assert.equal(by['9070 XT'].p50, 19.09); assert.equal(by['Intel UHD'].p50, 1.84); assert.equal(modelOf('AMD Radeon RX 9070 XT (OpenCL, gfx1201)'), '9070 XT'); assert.equal(modelOf('Apple M5 Max (Metal)'), 'M5 Max'); }); test('cliques finds the triangle and ignores the pendant', () => { const c = cliques(['a', 'b', 'c', 'd'], [['a', 'b'], ['b', 'c'], ['a', 'c'], ['c', 'd']], 3); assert.equal(c.length, 1); assert.deepEqual([...c[0]].sort(), ['a', 'b', 'c']); });