Files
wikideck/scripts/benchmark-randomx.ts

49 lines
3.7 KiB
TypeScript
Executable File

import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { arch, platform } from 'node:os';
import { RandomX } from '../server/testnet/randomx.ts';
import { MAX_TARGET, sha256 } from '../server/testnet/consensus.ts';
import { expectedHashes, targetForHashrate } from '../server/analysis/calibration.ts';
const args=process.argv.slice(2),known=new Set(['--consent','--fast','--hashes','--network-hps']);
for(let i=0;i<args.length;i++) {
if(!known.has(args[i]))throw new Error('Unknown option');
if(args[i]==='--hashes'||args[i]==='--network-hps')i++;
}
if(!args.includes('--consent'))throw new Error('Explicit --consent required: native RandomX consumes CPU, energy and ~256 MiB light / >2.3 GiB fast. No chain/network access.');
const number=(name:string,fallback:number,min:number,max:number)=>{
const i=args.indexOf(name),text=i<0?String(fallback):args[i+1];
const value=Number(text);
if(!text||!/^\d+$/.test(text)||!Number.isSafeInteger(value)||value<min||value>max)throw new Error(`Invalid ${name}`);
return value;
};
const count=number('--hashes',32,4,256),networkHps=number('--network-hps',1,1,1_000_000_000_000);
const binary=resolve(process.env.RANDOMX_BINARY??`native/build/wikideck-randomx${process.platform==='win32'?'.exe':''}`);
// Fingerprint the exact executable; it must come from the trusted pinned build, never a download URL.
const binarySha256=createHash('sha256').update(await readFile(binary)).digest('hex');
const engine=new RandomX({binary,fast:args.includes('--fast')});
const stop=()=>engine.close();process.once('SIGINT',stop);process.once('SIGTERM',stop);
const deadline=setTimeout(stop,60000);deadline.unref();
try {
const key=sha256('wikideck-offline-benchmark-epoch-a'),otherKey=sha256('wikideck-offline-benchmark-epoch-b');
const blob=Buffer.alloc(40);Buffer.from(sha256('wikideck-offline-benchmark-blob'),'hex').copy(blob);
let start=performance.now();const first=await engine.hash(key,blob);const coldMs=performance.now()-start;
start=performance.now();if(await engine.hash(key,blob)!==first)throw new Error('Repeated reference hash differs');
const repeatMs=performance.now()-start;
const samples:number[]=[];start=performance.now();
for(let i=0;i<count;i++) {
blob.writeBigUInt64LE(BigInt(i+1),32);const t=performance.now();await engine.hash(key,blob);samples.push(performance.now()-t);
}
const warmMs=performance.now()-start,warmHps=count/(warmMs/1000);
start=performance.now();await engine.hash(otherKey,blob);const epochSwitchMs=performance.now()-start;
samples.sort((a,b)=>a-b);
console.log(JSON.stringify({schema:'wikideck-randomx-benchmark-v1',mode:args.includes('--fast')?'fast':'light',
platform:platform(),arch:arch(),node:process.version,binarySha256,hashes:count,coldMs,repeatMs,warmMs,warmHashesPerSecond:warmHps,
p50HashMs:samples[Math.floor(samples.length*.5)],p95HashMs:samples[Math.min(samples.length-1,Math.floor(samples.length*.95))],epochSwitchMs,
currentInitialExpectedHashes:expectedHashes(MAX_TARGET),currentInitialExpectedSecondsAtMeasuredSingleWorker:expectedHashes(MAX_TARGET)/warmHps,
assumedNetworkHashesPerSecond:networkHps,illustrativeTargetForAssumedNetwork:targetForHashrate(networkHps),
note:'Local single-worker IPC-inclusive warm hash benchmark, not a network/miner-throughput estimate. Cold includes initialization; epoch switch is one cache change. Network H/s is an explicit assumption, not extrapolated from this machine.',
consensusChanged:false,productionReady:false},null,2));
} finally {clearTimeout(deadline);engine.close();process.off('SIGINT',stop);process.off('SIGTERM',stop);}