import { DatabaseSync } from 'node:sqlite'; import { mkdtemp, rm, stat } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { performance } from 'node:perf_hooks'; import { CanonicalIndex } from '../server/testnet/canonical-index.ts'; import { CHAIN_ID, GENESIS_HASH, sha256, transactionId } from '../server/testnet/consensus.ts'; import { NETWORK, type TestnetBlock, type NftState, type NftTransfer } from '../shared/testnet.ts'; // Synthetic LOCAL SQL dataset only. Not valid mined blocks; never submitted to Ledger or a peer. // This measures derived-index costs, not RandomX validation, consensus security or production SLOs. const text = process.argv[2] ?? '10000'; if (!/^[1-9]\d{0,5}$/.test(text) || Number(text) > 100000) throw new Error('Fixture size must be 1..100000'); const count = Number(text), queries = 1000; const directory = await mkdtemp(join(tmpdir(), 'wikideck-index-benchmark-')); const file = join(directory, 'fixture.sqlite'), db = new DatabaseSync(file); try { db.exec('PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA foreign_keys=ON; CREATE TABLE blocks(hash TEXT PRIMARY KEY,block_json TEXT NOT NULL)'); const index = new CanonicalIndex(db), chain: TestnetBlock[] = [], state: NftState = Object.create(null); index.initialize([], state); const owners = Array.from({ length: 16 }, (_, i) => sha256(`fixture-owner-${i}`)); const insert = db.prepare('INSERT INTO blocks VALUES(?,?)'); const start = performance.now(); db.exec('BEGIN IMMEDIATE'); try { for (let height = 1; height <= count; height++) { const hash = sha256(`fixture-block-${height}`), token = sha256(`fixture-token-${height}`), owner = owners[height % owners.length]; const txs: NftTransfer[] = height > 2 ? [{ network: NETWORK, chainId: CHAIN_ID, cardHash: chain[height - 3].tokenId, from: state[chain[height - 3].tokenId].owner, to: owner, sequence: 0, publicKey: '', signature: '' }] : []; const block = { height, hash, tokenId: token, previousHash: chain.at(-1)?.hash ?? GENESIS_HASH, owner, article: { title: `Synthetic index fixture ${height}`, pageId: height, extract: 'x'.repeat(512) }, transactions: txs } as unknown as TestnetBlock; state[token] = { owner, sequence: 0, mintedHeight: height, pageId: height }; for (const tx of txs) state[tx.cardHash] = { ...state[tx.cardHash], owner: tx.to, sequence: 1 }; insert.run(hash, JSON.stringify(block)); index.connect(block); chain.push(block); } db.exec('COMMIT'); } catch (error) { db.exec('ROLLBACK'); throw error; } const buildMs = performance.now() - start; const latency: number[] = []; for (let i = 0; i < queries; i++) { const block = chain[(i * 7919) % count], before = performance.now(); const nft = index.nft(block.tokenId, count); if (nft?.sequence !== state[block.tokenId].sequence || nft.owner !== state[block.tokenId].owner) throw new Error('Fixture ownership mismatch'); index.deck(owners[i % owners.length], 0, 24, count); if (block.transactions.length && !index.transaction(transactionId(block.transactions[0]))) throw new Error('Fixture transaction missing'); latency.push(performance.now() - before); } let cursor = 0, batches = 0; const consumeStart = performance.now(); do { const page = index.events(cursor, 64); cursor = page.cursor; batches++; } while (cursor < index.head()); const eventReadMs = performance.now() - consumeStart; const verifyStart = performance.now(); index.initialize(chain, state); const verifyMs = performance.now() - verifyStart; const bytes = (await stat(file)).size + (await stat(file + '-wal')).size; latency.sort((a, b) => a - b); console.log(JSON.stringify({ fixture: 'synthetic-derived-index-only', blocks: count, nfts: count, transactions: Math.max(0, count - 2), queries, buildMs, lookupGroupP50Ms: latency[499], lookupGroupP95Ms: latency[949], eventBatches: batches, maxEventsPerBatch: 64, eventReadMs, startupVerifyMs: verifyMs, databaseAndWalBytes: bytes, warning: 'No PoW, HTTP load, real assets, target hardware or production SLO certification.' }, null, 2)); } finally { db.close(); await rm(directory, { recursive: true, force: true }); }