77 lines
9.5 KiB
JavaScript
77 lines
9.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// proba-judecata.mjs: the judge of the market settlement (piata.mjs) in the laboratory, without a chain, on the timelines the live run
|
|
// recorded (dovezi/piata-28001-*.json, the newest): the honest timelines are VALID for their named status, and nine forged ones are
|
|
// INVALID for their named reason. --control-negativ plants every PAZA marker out, one at a time, in a copy of piata.mjs and requires
|
|
// the forged fixture it guards to be accepted: a check that cannot be made to fail is not a check.
|
|
// Exit 0 when every row is as named; 1 otherwise; 2 when no evidence file exists (run proba-piata-28001.mjs first).
|
|
// node proba-judecata.mjs [--control-negativ] env: AERE_PIATA_MODUL (the module under test; the control sets its planted copy)
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const MODUL = process.env.AERE_PIATA_MODUL || path.join(AICI, 'piata.mjs');
|
|
const control = process.argv.includes('--control-negativ');
|
|
const dovezi = fs.existsSync(path.join(AICI, 'dovezi')) ? fs.readdirSync(path.join(AICI, 'dovezi')).filter((f) => /^piata-28001-.*\.json$/.test(f)).sort() : [];
|
|
if (!dovezi.length) { console.log('NEMASURAT: no dovezi/piata-28001-*.json (run proba-piata-28001.mjs first)'); process.exit(2); }
|
|
const D = JSON.parse(fs.readFileSync(path.join(AICI, 'dovezi', dovezi[dovezi.length - 1]), 'utf8'));
|
|
const P = await import(pathToFileURL(MODUL).href + '?t=' + Date.now());
|
|
const clona = (x) => JSON.parse(JSON.stringify(x));
|
|
const date = (s) => { const x = D.scenarii[s]; if (!x || !x.date) throw new Error('scenario ' + s + ' has no timeline in the evidence'); return clona(x.date); };
|
|
const ev = (d, n) => d.events.find((e) => e.name === n);
|
|
|
|
/** The fixtures: honest timelines (VALID, named status) and forged ones (INVALID, named reason); `paza` names the marker that guards each forgery. */
|
|
export function fixturi(P) {
|
|
const F = [];
|
|
for (const [s, st] of [['S1', 'Paid'], ['S2', 'Slashed'], ['S3', 'Paid'], ['S4', 'Refunded'], ['S5', 'Refunded']]) F.push({ nume: `${s} the recorded timeline is VALID with status ${st}`, asteptat: 'VALID', r: () => { const r = P.judeca(date(s)); return { verdict: r.verdict, detail: r.status + (r.replay ? ' replay providerRight=' + r.replay.providerRight : '') + '; ' + r.motive.join('; ') }; } });
|
|
F.push({ nume: 'N1 S1 finalized INSIDE the window by a third party (timestamp moved back)', asteptat: 'INVALID', paza: 'fereastra', r: () => { const d = date('S1'); ev(d, 'JobPaid').timestamp = d.job.submittedAt + 1; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N2 S2 dispute raised AFTER the window (timestamp moved forward)', asteptat: 'INVALID', paza: 'fereastra', r: () => { const d = date('S2'); ev(d, 'JobDisputed').timestamp = d.job.submittedAt + d.job.challengeWindow + 5; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N3 S2 resolved by an address that is not the arbiter', asteptat: 'INVALID', paza: 'arbitru', r: () => { const d = date('S2'); ev(d, 'DisputeResolved').from = d.job.requester; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N4 S1 claimed after the deadline (deadline moved before the claim)', asteptat: 'INVALID', paza: 'termen', r: () => { const d = date('S1'); d.job.deadline = ev(d, 'JobClaimed').timestamp - 1; ev(d, 'JobPosted').args.deadline = String(d.job.deadline); return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N5 S1 paid to someone who is not the provider', asteptat: 'INVALID', paza: 'plata', r: () => { const d = date('S1'); ev(d, 'JobPaid').args.provider = d.job.requester; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N6 S1 result bytes that do not hash to the commitment', asteptat: 'INVALID', paza: 'rezultat', r: () => { const d = date('S1'); d.result = d.result.slice(0, -2) + (d.result.endsWith('00') ? '01' : '00'); return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N7 S3 the arbiter sided with the provider although the replay contradicts it (spec swapped to another seed)', asteptat: 'INVALID', paza: 'replay', r: () => { const d = date('S3'); d.spec = { ...d.spec, seed: '0x' + 'ab'.repeat(32) }; d.job.specHash = P.specHash(d.spec); ev(d, 'JobPosted').args.specHash = d.job.specHash; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N8 S1 the envelope names another result (outputHash changed, statementHash kept)', asteptat: 'INVALID', paza: 'dovada', r: () => { const d = date('S1'); d.envelope.statement.outputHash = '0x' + 'cc'.repeat(32); return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N9 S1 the envelope notarized only after the window closed', asteptat: 'INVALID', paza: 'dovada', r: () => { const d = date('S1'); d.envelope.notarization.firstSeen = d.job.submittedAt + d.job.challengeWindow + 1; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N10 S4 reclaimed as expired BEFORE the deadline', asteptat: 'INVALID', paza: 'termen', r: () => { const d = date('S4'); ev(d, 'JobRefunded').timestamp = d.job.deadline - 1; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N11 S2 the JobPosted event names another requester than the job', asteptat: 'INVALID', paza: 'postare', r: () => { const d = date('S2'); ev(d, 'JobPosted').args.requester = d.job.provider; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N12 S5 an event whose time is not a whole number of seconds: refused by name (form)', asteptat: 'INVALID', paza: 'forma', r: () => { const d = date('S5'); ev(d, 'JobPosted').timestamp += 0.5; return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N14 S1 finalized inside the window by a third party, with the ResultSubmitted event withheld (the window rule would be skipped): refused as not whole', asteptat: 'INVALID', paza: 'intreg', r: () => { const d = date('S1'); ev(d, 'JobPaid').timestamp = d.job.submittedAt + 1; d.events = d.events.filter((e) => e.name !== 'ResultSubmitted'); const r = P.judeca(d); return { verdict: r.verdict, detail: r.reguli.join() + ': ' + r.motive.join('; ') }; } });
|
|
F.push({ nume: 'N15 S4 the JobClaimed event withheld (the deadline rule of an expiry would be skipped): refused as not whole', asteptat: 'INVALID', paza: 'intreg', r: () => { const d = date('S4'); d.events = d.events.filter((e) => e.name !== 'JobClaimed'); return v(P.judeca(d)); } });
|
|
F.push({ nume: 'N13 malformed timelines (no constants, an event without args, events not a list) are refused by name, never thrown', asteptat: 'INVALID', r: () => { const rez = [(d) => { delete d.constants; }, (d) => { delete d.events[0].args; }, (d) => { d.events = 'x'; }].map((f) => { const d = date('S2'); f(d); const r = P.judeca(d); return r.verdict + ':' + r.reguli.join(); }); return { verdict: rez.every((x) => x === 'INVALID:forma') ? 'INVALID' : 'VALID', detail: rez.join(' ') }; } });
|
|
F.push({ nume: 'R1 the settlement receipt of S1 is an AIP-23 settlement envelope naming the payout tx and the provider', asteptat: 'VALID', r: () => { const d = date('S1'); const c = P.chitanta(d); const t = ev(d, 'JobPaid'); return { verdict: c.kind === 'aere-proof-of-settlement-attestation' && c.statement.txHash === t.txHash && c.statement.to.toLowerCase() === d.job.provider.toLowerCase() && c.statement.chainId === 28001 ? 'VALID' : 'INVALID', detail: c.statementHash.slice(0, 18) + '..' }; } });
|
|
return F;
|
|
}
|
|
const v = (r) => ({ verdict: r.verdict, detail: r.motive.join('; ') });
|
|
|
|
if (!control) {
|
|
let verzi = 0, rosii = 0, stricate = 0;
|
|
for (const f of fixturi(P)) {
|
|
let r; try { r = f.r(); } catch (e) { r = { verdict: 'STRICAT', detail: String(e.message).slice(0, 160) }; }
|
|
const stare = r.verdict === 'STRICAT' ? 'STRICAT' : r.verdict === f.asteptat ? 'VERDE' : 'ROSU'; stare === 'VERDE' ? verzi++ : stare === 'ROSU' ? rosii++ : stricate++;
|
|
console.log(` [${stare.padEnd(7)}] ${f.nume}: ${r.verdict}${r.detail ? ' (' + r.detail.slice(0, 150) + ')' : ''}`);
|
|
}
|
|
console.log(`\nmarket judge (evidence ${dovezi[dovezi.length - 1]}): ${verzi} VERDE, ${rosii} ROSU, ${stricate} STRICAT`);
|
|
process.exitCode = rosii || stricate ? 1 : 0;
|
|
} else {
|
|
const SURSA = fs.readFileSync(MODUL, 'utf8'); const marcaje = ['forma', 'intreg', 'postare', 'termen', 'fereastra', 'arbitru', 'plata', 'rezultat', 'replay', 'dovada']; let bune = 0;
|
|
for (const m of marcaje) {
|
|
const copie = path.join(AICI, `_plantat-piata-${process.pid}-${m}.mjs`); const n = SURSA.split(`/*PAZA:${m}*/`).length - 1; const tinte = fixturi(P).filter((f) => f.paza === m);
|
|
let stare, detaliu;
|
|
try {
|
|
if (n < 1 || !tinte.length) { stare = 'STRICAT'; detaliu = `marker ${n} times, ${tinte.length} fixtures`; }
|
|
else {
|
|
fs.writeFileSync(copie, SURSA.split(`/*PAZA:${m}*/`).join("|| String(1) === '1' /*PLANTAT*/"));
|
|
const Q = await import(pathToFileURL(copie).href + '?t=' + Date.now()); const fx = fixturi(Q); const rez = [];
|
|
for (const t of tinte) { const f = fx.find((x) => x.nume === t.nume); const r = f.r(); rez.push(`${t.nume.split(' ')[0]}:${r.verdict}`); }
|
|
const toate = rez.every((x) => x.endsWith(':VALID')); stare = toate ? 'VERDE' : 'ROSU'; detaliu = (toate ? 'the forged fixture is accepted with the check planted out: ' : 'the planting did not change the verdict: ') + rez.join(' ');
|
|
}
|
|
} catch (e) { stare = 'STRICAT'; detaliu = String(e.message).slice(0, 160); } finally { fs.rmSync(copie, { force: true }); }
|
|
if (stare === 'VERDE') bune++;
|
|
console.log(` [${stare.padEnd(7)}] PAZA:${m} (${n} marker${n === 1 ? '' : 's'}) ${detaliu}`);
|
|
}
|
|
const ramase = fs.readdirSync(AICI).filter((f) => f.startsWith('_plantat-piata-'));
|
|
console.log(`CONTROL: ${bune} of ${marcaje.length} checks proven load-bearing${ramase.length ? '; copies left: ' + ramase.join(',') : ''}`);
|
|
process.exitCode = bune === marcaje.length && !ramase.length ? 0 : 1;
|
|
}
|