// serveste.mjs: the read-only HTTP face of a verifiable dataset (roadmap point 20, "availability"): commitments, records with their // inclusion proofs, query answers with their completeness proofs, consistency proofs between versions. Every answer is checkable // against a commitment with verifica.mjs, without trusting this server. What this server proves is only that the bytes were served // when asked: availability sampling (verifica.mjs disponibilitate) draws random record positions and verifies each against the // commitment; a publisher that withholds a record fails the sample that lands on it. This is retrievability under sampling, not an // erasure-coded data-availability scheme. // GET /v1/vd//commitments every version's commitment (+ its AIP-23 envelope, notarized or not) // GET /v1/vd//commitment/ // GET /v1/vd//record// one record at version n with its log proof (all fields, or none with --privat) // GET /v1/vd//query/?field=f&eq=J | &from=J&to=J [&fields=a,b|*] [&limit=N] J = a JSON value ("x", 42, true, null) // GET /v1/vd//consistency// // Only GET; JSON; the dataset is re-read when versiuni.json changes; bodies are bounded by the query limit (default 1000 rows). import http from 'node:http'; import fs from 'node:fs'; import path from 'node:path'; import { deschide, versiune, dovadaInregistrare, interogheaza, dovadaConsistenta } from './baza.mjs'; const json = (res, cod, o) => { const b = Buffer.from(JSON.stringify(o)); res.writeHead(cod, { 'content-type': 'application/json', 'content-length': b.length, 'cache-control': 'no-store' }); res.end(b); }; const dinJson = (s) => { if (s === null || s === undefined) return undefined; try { return JSON.parse(s); } catch { throw new Error('a query value is JSON: "x", 42, true or null (got ' + String(s).slice(0, 40) + ')'); } }; /** Starts the server; resolves with { server, url, inchide }. `ascunde`: record positions the server withholds (a test hook, for the * availability control: a publisher that does not serve a record). */ export function porneste({ dir, port = 0, host = '127.0.0.1', privat = false, limit = 1000, ascunde = [] } = {}) { let db = deschide(dir); let mtime = fs.statSync(path.join(dir, 'versiuni.json')).mtimeMs; const plic = (n) => { const f = path.join(dir, 'plicuri', n + '.json'); return fs.existsSync(f) ? JSON.parse(fs.readFileSync(f, 'utf8')) : null; }; const recarca = () => { const m = fs.statSync(path.join(dir, 'versiuni.json')).mtimeMs; if (m !== mtime) { db = deschide(dir); mtime = m; } }; const ascunse = new Set(ascunde.map(Number)); const server = http.createServer((req, res) => { try { if (req.method !== 'GET') return json(res, 405, { error: 'method_not_allowed' }); const url = new URL(req.url, 'http://x'); const p = url.pathname.split('/').filter(Boolean); if (p[0] !== 'v1' || p[1] !== 'vd' || p.length < 4) return json(res, 404, { error: 'not_found' }); recarca(); if (p[2] !== db.set.name) return json(res, 404, { error: 'no_such_dataset' }); const n = (s) => { const x = Number(s); if (!Number.isInteger(x) || x < 0) throw new Error('not a non-negative integer: ' + s); return x; }; if (p[3] === 'health') return json(res, 200, { ok: true, name: db.set.name, versions: db.versiuni.length, records: db.inregistrari.length }); if (p[3] === 'commitments' && p.length === 4) return json(res, 200, { name: db.set.name, commitments: db.versiuni.map((c) => ({ commitment: c, envelope: plic(c.version) })) }); if (p[3] === 'commitment' && p.length === 5) { const c = versiune(db, n(p[4])); return json(res, 200, { commitment: c, envelope: plic(c.version) }); } if (p[3] === 'record' && p.length === 6) { const i = n(p[5]); if (ascunse.has(i)) return json(res, 404, { error: 'record_not_served' }); return json(res, 200, dovadaInregistrare(db, n(p[4]), i, { fields: privat ? [] : '*' })); } if (p[3] === 'query' && p.length === 5) { const q = url.searchParams; const fields = q.get('fields'); const lim = Math.min(Number(q.get('limit') || limit) || limit, limit); return json(res, 200, interogheaza(db, n(p[4]), { field: q.get('field'), eq: dinJson(q.get('eq')), from: dinJson(q.get('from')), to: dinJson(q.get('to')), fields: fields === '*' ? '*' : (fields ? fields.split(',').filter(Boolean) : []), limit: lim })); } if (p[3] === 'consistency' && p.length === 6) return json(res, 200, dovadaConsistenta(db, n(p[4]), n(p[5]))); return json(res, 404, { error: 'not_found' }); } catch (e) { return json(res, 400, { error: 'bad_request', detail: String(e.message).slice(0, 200) }); } }); return new Promise((ok, nu) => { server.on('error', nu); server.listen(port, host, () => { const a = server.address(); ok({ server, url: `http://${host}:${a.port}/v1/vd/${db.set.name}`, inchide: () => new Promise((r) => server.close(r)) }); }); }); }