import { CONSENSUS, type TestnetBlock } from '../../shared/testnet.ts'; import { CHAIN_ID, GENESIS_TIME, MAX_TARGET, expectedTarget, medianTime, workFor } from '../testnet/consensus.ts'; const SPACE = 1n << 256n; /** Analysis only. Calls the actual retarget/median helpers but DOES NOT construct valid blocks, * perform PoW, invoke Ledger, or provide a production fake-PoW selector. */ export type Scenario = 'steady' | 'rapid' | 'slow' | 'stall' | 'median-minimum' | 'future-edge'; export type TracePoint = { height: number; wallTime: number; timestamp: number; target: string; cumulativeWork: string; expectedHashes: number; nextTarget: string }; export function expectedHashes(target: string): number { if (!/^[a-f0-9]{64}$/.test(target)) throw new Error('Invalid target'); return Number(SPACE) / Number(BigInt(`0x${target}`) + 1n); } export function targetForHashrate(hashesPerSecond: number, seconds = CONSENSUS.targetSeconds): string { if (!Number.isSafeInteger(hashesPerSecond) || hashesPerSecond < 1 || hashesPerSecond > 1_000_000_000_000 || !Number.isSafeInteger(seconds) || seconds < 1 || seconds > 86400) throw new Error('Hashrate/interval outside analysis bounds'); const denominator = BigInt(hashesPerSecond) * BigInt(seconds); const target = SPACE / denominator - 1n; const maximum = BigInt(`0x${MAX_TARGET}`); return (target < 1n ? 1n : target > maximum ? maximum : target).toString(16).padStart(64,'0'); } export function simulate(scenario: Scenario, count = 60): TracePoint[] { if (!['steady','rapid','slow','stall','median-minimum','future-edge'].includes(scenario) || !Number.isSafeInteger(count) || count < 1 || count > 1000) throw new Error('Invalid bounded simulation'); const history: TestnetBlock[] = [], result: TracePoint[] = []; let wallTime = GENESIS_TIME, cumulativeWork = 0n; for (let i = 0; i < count; i++) { const target = expectedTarget(history); const spacing = scenario === 'rapid' ? 1 : scenario === 'slow' ? CONSENSUS.targetSeconds * 8 : scenario === 'stall' && i === 20 ? 7 * 86400 : CONSENSUS.targetSeconds; wallTime += spacing; const timestamp = scenario === 'median-minimum' ? medianTime(history) + 1 : scenario === 'future-edge' ? wallTime + CONSENSUS.maxFutureSeconds : wallTime; if (timestamp <= medianTime(history) || timestamp > wallTime + CONSENSUS.maxFutureSeconds) throw new Error('Analysis generated an invalid timestamp'); // Only these fields are read by expectedTarget/medianTime. Never submit these records to a node. history.push({ createdAt: timestamp, target } as TestnetBlock); cumulativeWork += workFor(target); result.push({ height:i+1,wallTime,timestamp,target,cumulativeWork:cumulativeWork.toString(), expectedHashes:expectedHashes(target),nextTarget:expectedTarget(history) }); } return result; } /** Idealized fixed-difficulty Poisson race: probability of ever catching up from a CURRENT * known z-block deficit, q