Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit ecd798747a26d339ff514da04773be858780636c)
106 lines
5.9 KiB
JavaScript
Executable file
106 lines
5.9 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
// A GPU worker stand-in that speaks the miner's serve protocol (ready / job / done / found / prepare / prepared /
|
|
// quit) and misbehaves on command, so the miner's fault guards and the app's watchdog can be measured on a Mac with
|
|
// no faulty GPU at hand. It never hashes: every `done` is invented, every `found` is wrong on purpose.
|
|
//
|
|
// FAKE_WORKER_CTL=<file> the file's first word is the mode, re-read before every job:
|
|
// ok every job: `done` after 100 ms with the job's nonce count as hashes (about 167 MH/s at 2^24 nonces)
|
|
// slow `done` after 5,000 ms (a self-heal build inside a job, a game on the GPU)
|
|
// fast `done` after 0.3 ms with the full count: a runtime answering success while running nothing
|
|
// (the 4 October 2026 gfx1036 fault)
|
|
// zero `done` after 100 ms with 0 hashes: the worker completes jobs and produces nothing
|
|
// silent never answers a job (the queue fills and nothing comes back)
|
|
// badfound a `found` with a wrong hash before every `done`: a worker on a wrong program
|
|
// preparefail every `prepare` is answered `prepare-failed`; jobs run as in ok
|
|
// exit the process exits with code 7 at the next job
|
|
// absent `--list` shows no device (the card is unplugged or in a problem code); jobs run as in ok
|
|
// selftest the worker prints a self-test failure and exits 3 before ready (MF-4)
|
|
// Extra lines in the control file: `devices N` (how many --list shows), `dev N <mode>` (a mode for device N only)
|
|
// The mode is read from argv `--mode <m>` when no control file is set. `--serve` is accepted and ignored.
|
|
// Lines on stderr are prefixed `fake-worker:` and never match a miner pattern.
|
|
|
|
import { readFileSync, existsSync, writeSync } from 'node:fs';
|
|
import { createRequire } from 'node:module';
|
|
const require = createRequire(import.meta.url);
|
|
import { createInterface } from 'node:readline';
|
|
|
|
// `--list` (the engine's OpenCL enumeration on Windows and Linux, src/detect.rs): one fake GPU, or none while the
|
|
// control file says `absent` (the hot-plug step: a card that appears later must start without a tap)
|
|
if (process.argv.includes('--list')) {
|
|
const m = (() => { try { return require('node:fs').readFileSync(process.env.FAKE_WORKER_CTL, 'utf8').trim().split(/\s+/)[0]; } catch { return 'ok'; } })();
|
|
const n = m === 'absent' ? 0 : (() => { try { const l = require('node:fs').readFileSync(process.env.FAKE_WORKER_CTL, 'utf8').split('\n').map(x => x.trim().split(/\s+/)).find(f => f[0] === 'devices'); return l ? Number(l[1]) : 1; } catch { return 1; } })();
|
|
// the real worker's header line: the engine reads a listing without it as "the tool did not answer" and removes
|
|
// no card on it (src/detect.rs parse_opencl_list), which is right for a crashed driver and wrong for a stand-in
|
|
process.stdout.write(`OpenCL devices (${n}):\n`);
|
|
for (let i = 0; i < n; i++) {
|
|
process.stdout.write(`[${i}] Fake GPU ${i + 1} | Fake Platform (OpenCL 1.2)\n GPU, vendor Fake Silicon, driver 1.0.0, OpenCL C 1.2, 8 compute units, 8192 MB\n`);
|
|
}
|
|
process.exit(0);
|
|
}
|
|
const ctl = process.env.FAKE_WORKER_CTL;
|
|
const argMode = (() => { const i = process.argv.indexOf('--mode'); return i >= 0 ? process.argv[i + 1] : 'ok'; })();
|
|
// the device this process serves (`--device N` from the engine's OpenCL path); the control file's first word is the
|
|
// mode for every device, a line `dev N <mode>` overrides it for device N, a line `devices N` sets how many --list shows
|
|
const device = (() => { const i = process.argv.indexOf('--device'); return i >= 0 ? process.argv[i + 1] : '0'; })();
|
|
function mode() {
|
|
if (ctl && existsSync(ctl)) {
|
|
const lines = readFileSync(ctl, 'utf8').trim().split('\n');
|
|
const own = lines.map(l => l.trim().split(/\s+/)).find(f => f[0] === 'dev' && f[1] === device);
|
|
if (own && own[2]) return own[2];
|
|
const m = (lines[0] || '').trim().split(/\s+/)[0];
|
|
if (m) return m;
|
|
}
|
|
return argMode;
|
|
}
|
|
function deviceCount() {
|
|
try { const l = readFileSync(ctl, 'utf8').split('\n').map(x => x.trim().split(/\s+/)).find(f => f[0] === 'devices'); return l ? Number(l[1]) : 1; } catch { return 1; }
|
|
}
|
|
// synchronous writes: a piped stdout is asynchronous in Node and process.exit would drop pending lines
|
|
const out = (s) => { writeSync(1, s + '\n'); };
|
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
|
|
if (mode() === 'selftest') {
|
|
// MF-4: the worker fails its self-test and exits before it is ready (the Arc B580 on PC 1, 7 October 2026)
|
|
out('error 0 self-test FAIL: 3 of 96 vectors mismatched (fake worker, device ' + device + ')');
|
|
process.exit(3);
|
|
}
|
|
out('ready fake Fake_GPU dataset-log2 28 batch 16777216 prepare 1');
|
|
|
|
const queue = [];
|
|
let running = false;
|
|
async function pump() {
|
|
if (running) return;
|
|
running = true;
|
|
while (queue.length) {
|
|
const f = queue.shift();
|
|
const [id, , , nonceStart, nonces] = f;
|
|
const m = mode();
|
|
if (m === 'exit') process.exit(7);
|
|
if (m === 'silent') { await sleep(1_000_000); continue; }
|
|
const t0 = performance.now();
|
|
let hashes = Number(nonces);
|
|
let ms = 100;
|
|
if (m === 'slow') ms = 5000;
|
|
else if (m === 'fast') ms = 0.3;
|
|
else if (m === 'zero') hashes = 0;
|
|
await sleep(ms);
|
|
if (m === 'badfound') out(`found ${id} ${BigInt(nonceStart) + 17n} 0000000000000001`);
|
|
out(`done ${id} ${hashes} ${(performance.now() - t0).toFixed(2)}`);
|
|
}
|
|
running = false;
|
|
}
|
|
|
|
const rl = createInterface({ input: process.stdin });
|
|
rl.on('line', (line) => {
|
|
const f = line.trim().split(/\s+/);
|
|
switch (f[0]) {
|
|
case 'job': queue.push(f.slice(1)); pump(); break;
|
|
case 'prepare':
|
|
if (mode() === 'preparefail') out(`prepare-failed ${f[1]} ${f[2]} fake worker refuses`);
|
|
else setTimeout(() => out(`prepared ${f[1]} ${f[2]} 50 fake resident 2 programs 2 datasets`), 50);
|
|
break;
|
|
case 'quit': process.exit(0);
|
|
default: break;
|
|
}
|
|
});
|
|
rl.on('close', () => process.exit(0));
|