112 lines
13 KiB
JavaScript
112 lines
13 KiB
JavaScript
// Controlul negativ al portofelului de plati x402 (proba-wallet.mjs), al modului cosign 2-din-2 (proba-cosign.mjs) si al verificatorului de plati
|
|
// (proba-verifica-plati.mjs, inclusiv codul portofelului-contract): fiecare paznic se strica intr-o COPIE (agent-policy/*.mjs si
|
|
// x402/*.mjs, in aceeasi asezare), proba ruleaza pe copie si trebuie sa iasa rosie EXACT pe verificarea numita, cu proba chiar rulata.
|
|
// Plantarile sunt conditii false la rulare sau randuri scoase, deci copia se incarca intotdeauna; o ancora care nu apare exact o data,
|
|
// sau o proba care nu ajunge la rezumat, e un esec al controlului (STRICAT), nu o linie informativa.
|
|
// node control-negativ-wallet.mjs iesire 0 = martorul verde si toate plantarile rosii pe proba lor
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawn } from 'node:child_process';
|
|
import { createRequire } from 'node:module';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const SUS = path.resolve(AICI, '..');
|
|
// ethers se rezolva de langa original (copia nu are node_modules); calea se da copiei prin AERE_ETHERS
|
|
let ETHERS = process.env.AERE_ETHERS || null; // o copie a acestui dosar (controlul portii) primeste calea din mediu
|
|
if (!ETHERS) try { ETHERS = createRequire(path.join(AICI, 'wallet.mjs')).resolve('ethers'); }
|
|
catch { try { ETHERS = createRequire(path.join(AICI, 'wallet.mjs')).resolve(path.resolve(SUS, '..', '..', 'contracts', 'node_modules', 'ethers')); } catch { console.log('NEMASURAT: ethers nu se gaseste (npm install aici, sau AERE_ETHERS=<cale>)'); process.exit(2); } }
|
|
const V = 'proba-verifica-plati.mjs', K = 'proba-cosign.mjs';
|
|
const PLANTARI = [
|
|
// [nume, fisier (relativ la x402/), tipar, inlocuire, textul verificarii care trebuie sa iasa rosie]
|
|
['cererile nu mai sunt judecate una cate una', 'wallet.mjs', 'function authorize(x) { const r = coada.then(() => autorizeaza(x)); coada = r.catch(() => {}); return r; }', 'function authorize(x) { return autorizeaza(x); }', '12. ATAC'],
|
|
['registrul nu mai trebuie sa verifice', 'wallet.mjs', ' if (!v.ok) {', " if (!v.ok && process.env.AERE_PLANTA_NICIODATA === 'da') {", '3. ATAC'],
|
|
['intrarea nu mai trebuie sa numeasca aceasta cumparatura', 'wallet.mjs', "for (const k of ['from', 'to', 'amount', 'asset', 'ref']) if (", 'for (const k of []) if (', '4. ATAC'],
|
|
['aceeasi intrare poate plati de doua ori', 'wallet.mjs', 'if (S.last && e.seq <= S.last.seq) return refuz(', "if (S.last && e.seq <= S.last.seq && process.env.AERE_PLANTA_NICIODATA === 'da') return refuz(", '5. ATAC'],
|
|
['intrarea nu mai trebuie sa fie de acum', 'wallet.mjs', 'if (!(Number(b.at) >= t - TOL && Number(b.at) <= t + 5)) return', "if (!(Number(b.at) >= t - TOL && Number(b.at) <= t + 5) && process.env.AERE_PLANTA_NICIODATA === 'da') return", '10. ATAC'],
|
|
['limita nu mai e judecata fata de ce a semnat portofelul', 'wallet.mjs', 'stare.signed.map((s) => ({ amount: s.amount, at: s.at }))', '[]', '7. politica noua'],
|
|
['aprobarile intrarii nu mai sunt date limitei portofelului', 'wallet.mjs', 'if (r.ok) aprobatori.push(r.humanId); }', '}', '9. 30000'],
|
|
['dovada de echivocare nu mai e data', 'wallet.mjs', 'if (p.agentId === policy.agentId && verifyEquivocation(p).ok) r.equivocation = p;', '', '6. si portofelul da dovada'],
|
|
['nonce-ul autorizarii nu mai numeste intrarea', 'wallet.mjs', 'nonce: nonceForEntry(e.hash) };', "nonce: '0x' + crypto.randomBytes(32).toString('hex') };", '1. nonce-ul fiecarei'],
|
|
['revocarile primite nu mai ajung la verificarea registrului', 'wallet.mjs', 'revocations: stare.revocations, anchors', 'revocations: [], anchors', '8. dupa revocare'],
|
|
['serverul de resurse primeste o plata pentru alta cerinta', 'resource-server.mjs', 'if (cheie(payload.accepted) !== cheie(requirement)) return cere(', "if (cheie(payload.accepted) !== cheie(requirement) && process.env.AERE_PLANTA_NICIODATA === 'da') return cere(", '11. CONTROL'],
|
|
['clientul cere semnatura si cand politica a refuzat', 'client.mjs', "if (!r.allowed) return { paid: false, status: 402, reason: `the agent's policy refused the payment: ${r.reason}`, entry: r.entry };", '', '2. CONTROL'],
|
|
['portofelul porneste si fara limita in activul lui', 'wallet.mjs', 'if (!policy.spend || policy.spend.asset !== assetId(network, token)) throw', "if ((!policy.spend || policy.spend.asset !== assetId(network, token)) && process.env.AERE_PLANTA_NICIODATA === 'da') throw", 'o politica fara limita'],
|
|
// modul cosign (portofel-contract 2-din-2): ce verifica agentul inainte sa-si adauge jumatatea de semnatura
|
|
['agentul semneaza o autorizare din alt portofel', 'client.mjs', 'if (!eq(a.from, status.address)) return', 'if (false) return', 'schimba platitorul', K],
|
|
['agentul semneaza alt destinatar sau alta suma', 'client.mjs', 'if (!eq(a.to, req.payTo) || String(a.value) !== String(req.amount)) return', 'if (false) return', 'schimba destinatarul', K],
|
|
['agentul semneaza alt nonce decat intrarea lui', 'client.mjs', 'if (!eq(a.nonce, nonceForEntry(entryHash))) return', 'if (false) return', 'alt nonce', K],
|
|
['agentul semneaza un termen oricat de lung', 'client.mjs', 'if (!(Number(a.validBefore) > now && Number(a.validBefore) <= now + Number(req.maxTimeoutSeconds) + 60)) return', 'if (false) return', 'lungeste termenul', K],
|
|
['agentul nu mai compara digestul cu al lui', 'client.mjs', 'if (!cosign || digest !== cosign.digest) return', 'if (!cosign) return', 'alt digest', K],
|
|
['agentul nu mai cere jumatatea semnatarului declarat', 'client.mjs', 'if (!semnatarPolitica || !eq(semnatarPolitica, status.policySigner)) return', 'if (false) return', 'alta cheie decat semnatarul declarat', K],
|
|
// verificatorul platilor, pe raspunsurile inregistrate de pe 28001
|
|
['verificatorul nu mai cere ca registrul sa verifice', 'verifica-plati.mjs', 'entries)`, v.ok, v.error', 'entries)`, true, v.error', '2. CONTROL: o suma schimbata', V],
|
|
['verificatorul nu mai cere ca intrarea platii sa fie in registru', 'verifica-plati.mjs', '!!e && e.hash === p.entryHash)', '!!e)', '3. CONTROL', V],
|
|
['verificatorul nu mai cere portofelul in intrare', 'verifica-plati.mjs', "String(a.from).toLowerCase() === wallet && ", '', '4. CONTROL', V],
|
|
['verificatorul nu mai cere status 1', 'verifica-plati.mjs', "!!rc && rc.status === '0x1'", '!!rc', '5. CONTROL', V],
|
|
['verificatorul nu mai cere nonce-ul intrarii pe lant', 'verifica-plati.mjs', '&& l.topics[2].toLowerCase() === nonce));', '));', '6. CONTROL', V],
|
|
['verificatorul nu mai cere suma din Transfer', 'verifica-plati.mjs', '&& BigInt(l.data) === BigInt(a.amount)));', '));', '7. CONTROL', V],
|
|
['verificatorul nu mai cere ca orice Transfer sa fie in registru', 'verifica-plati.mjs', 'straine.length === 0,', 'true,', '8. CONTROL', V],
|
|
['verificatorul nu mai cere lantul dosarului', 'verifica-plati.mjs', 'lantul === chain,', 'true,', '9. CONTROL', V],
|
|
// --all-transfers pe o usa care refuza intervalele mari (2026-10-21, trecerea straina): paginarea si clasificarea fiecarei ferestre
|
|
['verificatorul nu mai pagineaza (o singura cerere de la fromBlock la cap)', 'verifica-plati.mjs', 'for (let a = from; a <= to; a += window) ferestre.push([a, Math.min(a + window - 1, to)]);',
|
|
"if (process.env.AERE_PLANTA_NICIODATA === 'da') for (let a = from; a <= to; a += window) ferestre.push([a, Math.min(a + window - 1, to)]); else ferestre.push([from, to]);", '15. usa cu limita', V],
|
|
['o fereastra cu eroare se numara drept goala', 'verifica-plati.mjs', 'if (j && Array.isArray(j.result)) { peFereastra[k] = j.result; return; }',
|
|
"if ((j && Array.isArray(j.result)) || process.env.AERE_PLANTA_NICIODATA !== 'da') { peFereastra[k] = j && Array.isArray(j.result) ? j.result : []; return; }", '18. CONTROL', V],
|
|
['un rezultat care nu e lista se numara drept gol', 'verifica-plati.mjs', 'if (j && Array.isArray(j.result)) { peFereastra[k] = j.result; return; }',
|
|
'if (j && !j.error && !j.transport) { peFereastra[k] = Array.isArray(j.result) ? j.result : []; return; }', '19. CONTROL', V],
|
|
['o fereastra necitita ascunde un Transfer strain gasit', 'verifica-plati.mjs', 'if (straine.length || !r.unreadable) {', "if ((straine.length && process.env.AERE_PLANTA_NICIODATA === 'da') || !r.unreadable) {", '21. CONTROL', V],
|
|
['plafonul de cereri nu mai e respectat', 'verifica-plati.mjs', 'if (ferestre.length > maxRequests) return', "if (ferestre.length > maxRequests && process.env.AERE_PLANTA_NICIODATA === 'da') return", '22. CONTROL', V],
|
|
['un cap sub fromBlock e luat drept interval gol', 'verifica-plati.mjs', '|| to < from) nu(', "|| (to < from && process.env.AERE_PLANTA_NICIODATA === 'da')) nu(", '23. CONTROL', V],
|
|
// portofelul-contract 2-din-2 in dosar: codul de pe lant
|
|
['verificatorul nu mai judeca portofelul-contract', 'verifica-plati.mjs', ' if (d.walletContract) {', " if (d.walletContract && process.env.AERE_PLANTA_NICIODATA === 'da') {", '14. CONTROL', V],
|
|
['verificatorul cere doar lungimea codului, nu codul', 'verifica-plati.mjs', 'cod === asteptat, cod === asteptat ?', 'cod.length === asteptat.length, cod === asteptat ?', '12. CONTROL', V],
|
|
['codul asteptat nu mai poarta semnatarii din dosar', 'contract-2of2.mjs', 'cuvant.copy(cod, p.start); }', '}', '11. dosarul portofelului 2-din-2', V],
|
|
];
|
|
function copie() {
|
|
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-wallet-ctl-'));
|
|
for (const f of fs.readdirSync(SUS).filter((n) => n.endsWith('.mjs'))) fs.copyFileSync(path.join(SUS, f), path.join(t, f));
|
|
fs.mkdirSync(path.join(t, 'x402'));
|
|
// modulele si artefactul contractului 2-din-2 (verificatorul il citeste de langa el); nu package*.json si nu node_modules
|
|
for (const f of fs.readdirSync(AICI).filter((n) => n.endsWith('.mjs') || n === 'AereAgentWallet2of2.json')) fs.copyFileSync(path.join(AICI, f), path.join(t, 'x402', f));
|
|
fs.cpSync(path.join(AICI, 'dovezi-28001'), path.join(t, 'x402', 'dovezi-28001'), { recursive: true });
|
|
return t;
|
|
}
|
|
// probele ruleaza cate PARALEL deodata (asincron), ca tot controlul sa incapa in termenul rulatorului comun
|
|
const PARALEL = 4;
|
|
function ruleaza(t, proba = 'proba-wallet.mjs') {
|
|
return new Promise((resolve) => {
|
|
const c = spawn(process.execPath, [path.join(t, 'x402', proba)], { env: { ...process.env, AERE_ETHERS: ETHERS } }); let out = '';
|
|
const ceas = setTimeout(() => c.kill(), 240000);
|
|
c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; });
|
|
c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /(agent-wallet|verifica-plati|agent-cosign): \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => /^\s+RAU\s/.test(l)) }); });
|
|
});
|
|
}
|
|
async function inGrup(lucrari) { const rez = new Array(lucrari.length); let i = 0;
|
|
await Promise.all(Array.from({ length: PARALEL }, async () => { while (i < lucrari.length) { const k = i++; rez[k] = await lucrari[k](); } })); return rez; }
|
|
let esecuri = 0;
|
|
const martori = await inGrup(['proba-wallet.mjs', V, K].map((proba) => async () => { const t0 = copie(); try { return [proba, await ruleaza(t0, proba)]; } finally { fs.rmSync(t0, { recursive: true, force: true }); } }));
|
|
for (const [proba, m0] of martori) {
|
|
if (m0.cod === 0 && m0.rulat && !m0.rosii.length) console.log(` OK martorul ${proba}: copia neatinsa verde`);
|
|
else { esecuri++; console.log(` RAU martorul ${proba} nu e verde (cod ${m0.cod}, ${m0.rulat ? m0.rosii.length + ' RAU' : 'nu a ajuns la rezumat'})`); }
|
|
}
|
|
const linii = await inGrup(PLANTARI.map(([nume, f, din, inl, tinta, proba = 'proba-wallet.mjs']) => async () => {
|
|
let t = null;
|
|
try {
|
|
t = copie(); const fp = path.join(t, 'x402', f);
|
|
if (!fs.existsSync(fp)) return [false, ` RAU ${nume}: plantarea numeste un fisier care nu exista (${f})`];
|
|
const src = fs.readFileSync(fp, 'utf8');
|
|
if (src.split(din).length !== 2) return [false, ` RAU ${nume}: tiparul apare de ${src.split(din).length - 1} ori in ${f} (STRICAT)`];
|
|
fs.writeFileSync(fp, src.replace(din, inl));
|
|
const r = await ruleaza(t, proba);
|
|
if (!r.rulat) return [false, ` RAU ${nume}: proba nu a ajuns la rezumat (STRICAT, cod ${r.cod})`];
|
|
if (r.cod !== 0 && r.rosii.some((l) => l.includes(tinta))) return [true, ` OK ${nume}: '${tinta}' ROSIE (${r.rosii.length} RAU)`];
|
|
return [false, ` RAU ${nume}: '${tinta}' a ramas verde (${r.rosii.length} RAU altundeva)`];
|
|
} catch (e) { return [false, ` RAU ${nume}: controlul a cazut pe ea (${String(e.message).slice(0, 80)})`]; }
|
|
finally { if (t) fs.rmSync(t, { recursive: true, force: true }); }
|
|
}));
|
|
for (const [bun, l] of linii) { console.log(l); if (!bun) esecuri++; }
|
|
console.log(esecuri ? `RAU: ${esecuri} esecuri ale controlului` : `DOVEDIT: martorii verzi, ${PLANTARI.length} din ${PLANTARI.length} plantari rosii pe verificarea lor`);
|
|
process.exitCode = esecuri ? 1 : 0;
|