49 lines
3.7 KiB
TypeScript
Executable File
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);}
|