Zum Inhalt

Approval Gateway

Datei: cli/approve_settlement.ts (122 Zeilen) Zweck: Sicherheitsvalidierung vor dem On-Chain-Commit Aufgerufen von: cli/settle.ts (Schritt 4)

Übersicht

Das Approval Gateway führt 7 Validierungs-Gates + 1 Warning durch, bevor ein Settlement für den Blockchain-Commit freigegeben wird. Jedes Gate muss bestehen - bei einem Fehler wird der gesamte Prozess abgebrochen.

dry_run Settlement
   ┌─── G1: Existenz ──────────┐
   ├─── G2: Eindeutigkeit ─────┤
   ├─── G3: Winner-Summe ──────┤
   ├─── G4: Affiliate-Summe ───┤
   ├─── G5: Pool-Summe ────────┤
   ├─── G6: Merkle-Root ───────┤
   ├─── G7: Affiliate-Root ────┤
   ├─── G8: JackpotHitArr ─────┤
   └─── W1: Charity-Sanity ────┘ (Warning only)
   status = 'approved'

Gate-Details

G1 + G2: Existenz und Eindeutigkeit

// approve_settlement.ts:30-45
// Exakt 1 dry_run Settlement muss existieren
const runs = await pg.query(
  `SELECT * FROM settlement_runs
   WHERE day_id = $1 AND status = 'dry_run'`,
  [dayId]
);

if (runs.rows.length === 0) throw new Error("G1: Kein dry_run Settlement");
if (runs.rows.length > 1)  throw new Error("G2: Mehrere dry_run Settlements");

Zweck: Verhindert Commits ohne Berechnung oder bei inkonsistentem Zustand.

G3: Winner-Summe

// approve_settlement.ts:48-59
const winnerSum = await pg.query(
  `SELECT COALESCE(SUM(amount), 0) AS total
   FROM settlement_winner_payouts
   WHERE settlement_run_id = $1`,
  [runId]
);

if (winnerSum !== run.total_payout) {
  throw new Error(`G3: Winner-Summe ${winnerSum} != totalPayout ${run.total_payout}`);
}

Invariante: SUM(winner_payouts.amount) === settlement_runs.total_payout

G4: Affiliate-Summe

// approve_settlement.ts:62-73
const affiliateSum = await pg.query(
  `SELECT COALESCE(SUM(amount), 0) AS total
   FROM settlement_affiliate_payouts
   WHERE settlement_run_id = $1`,
  [runId]
);

if (affiliateSum !== run.affiliate_total) {
  throw new Error(`G4: Affiliate-Summe ${affiliateSum} != affiliateTotal ${run.affiliate_total}`);
}

Invariante: SUM(affiliate_payouts.amount) === settlement_runs.affiliate_total

G5: Pool-Summe

// approve_settlement.ts:76-80
const poolSum = poolPayoutArr.reduce((a, b) => a + b, 0n)
              + poolJackpotPayoutArr.reduce((a, b) => a + b, 0n);

if (poolSum !== run.total_payout) {
  throw new Error(`G5: Pool-Summe ${poolSum} != totalPayout ${run.total_payout}`);
}

Invariante: SUM(poolPayout) + SUM(poolJackpotPayout) === totalPayout

G6: Merkle-Root

// approve_settlement.ts:83-85
if (run.merkle_root === ZERO_BYTES32) {
  throw new Error("G6: merkleRoot ist 0x0");
}

Invariante: Winners-Merkle-Root darf niemals Null sein (Dummy-Leaf bei 0 Gewinnern).

G7: Affiliate-Root-Konsistenz

// approve_settlement.ts:87-92
if (run.affiliate_total === 0n && run.affiliate_root !== ZERO_BYTES32) {
  throw new Error("G7: affiliateTotal=0 aber affiliateRoot != 0x0");
}
if (run.affiliate_total > 0n && run.affiliate_root === ZERO_BYTES32) {
  throw new Error("G7: affiliateTotal>0 aber affiliateRoot = 0x0");
}

G8: JackpotHitArr-Format

// approve_settlement.ts:95-97
if (!Array.isArray(run.jackpot_hit_arr) || run.jackpot_hit_arr.length !== 5) {
  throw new Error("G8: jackpotHitArr muss Array[5] sein");
}

W1: Charity-Sanity (Warning)

// approve_settlement.ts:100-102
if (run.charity_total > run.affiliate_total * 2n) {
  console.warn("W1: Charity-Total ungewöhnlich hoch");
  // Kein Abbruch - nur Warning
}

Aktionen nach Genehmigung

// approve_settlement.ts:105-112
await pg.query(
  `UPDATE settlement_runs
   SET status = 'approved',
       approved_at = NOW()
   WHERE id = $1`,
  [runId]
);

Status-Transition: dry_runapproved

Gate-Übersicht

Gate Prüfung Schweregrad
G1 Existiert ein dry_run Settlement? Fatal
G2 Genau 1 dry_run (nicht mehr)? Fatal
G3 Winner-Auszahlungssumme = totalPayout? Fatal
G4 Affiliate-Auszahlungssumme = affiliateTotal? Fatal
G5 Pool-Auszahlungssumme = totalPayout? Fatal
G6 Merkle-Root != 0x0? Fatal
G7 Affiliate-Root-Konsistenz? Fatal
G8 jackpotHitArr ist Array[5]? Fatal
W1 Charity plausibel? Warning

Fehlermeldungen

Alle Gates werfen sprechende Fehlermeldungen mit konkreten Werten:

G3: Winner-Summe 15000000000 != totalPayout 15000000001

Dies ermöglicht schnelle Diagnose bei Berechnungsfehlern.