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