Settlement Pipeline¶
Datei:
cli/settle.ts(120 Zeilen) Trigger: Cron-Job täglich um 00:00 UTC Tracking:settlement_pipeline_runs-Tabelle
Übersicht¶
Die Settlement Pipeline ist der zentrale Tagesabschluss-Prozess. Sie orchestriert 8 sequentielle Schritte von der Datenextraktion bis zur Blockchain-Transaktion.
00:00 UTC ┌─── settle.ts startet
│
├── waitUntilDayIsReady() Warten auf finalisierte Blöcke
│
├── 1. export_inputsDB.ts Gewinner aus DB → Artefakt
├── 2. export_affiliatesDB.ts Affiliates aus DB → Artefakt
├── 3. run.ts Berechnung + Merkle-Trees
├── 4. approve_settlement.ts 7 Validierungs-Gates
├── 5. commit_from_db.ts On-Chain-Commit
│
├── 6. updatePotSizes.ts Pot-Sync (non-fatal)
├── 7. refresh-global-stats.ts Dashboard-Stats (non-fatal)
└── 8. notifyWinners.ts Push-Notifications (non-fatal)
Day-Readiness-Check¶
// settle.ts:14-44
async function waitUntilDayIsReady() {
// Warten bis der aktuelle Block-Timestamp >= dayEnd + 15min Buffer
// Verhindert Settlement auf nicht-finalisierte Blöcke
while (true) {
const block = await provider.getBlock("finalized");
if (block.timestamp >= dayEndTimestamp + 900) break; // 15min Buffer
await sleep(60_000); // 1 Minute warten
}
}
Warum 15 Minuten Buffer?
- Arbitrum-Blöcke können Timestamp-Lag haben
- Finalisierte Blöcke sind ~10-15 Minuten hinter Realzeit
- Buffer verhindert fehlende Tickets durch späte Blöcke
Pipeline-Schritte im Detail¶
Schritt 1: Export Inputs¶
Datei:
cli/export_inputsDB.ts(199 Zeilen)
Exportiert alle Gewinner-Tickets des Tages aus der Datenbank:
SELECT ticket_id, player, pool_id, matches, class_id
FROM tickets t
JOIN ticket_tips tt ON tt.ticket_id = t.ticket_id
WHERE day_id = $1 AND class_id IN (2, 3, 4, 5)
ORDER BY pool_id, class_id, ticket_id
Output: settlement_artifacts-Tabelle + artifacts/dayId=X/inputs.json
{
"schemaVersion": 1,
"dayId": 20500,
"winnersByPool": {
"0": { "2": [...], "3": [...], "4": [...], "5": [...] },
"1": { ... }
}
}
Schritt 2: Export Affiliates¶
Datei:
cli/export_affiliatesDB.ts(195 Zeilen)
Exportiert Affiliate-Provisionen des Tages:
SELECT ticket_id, affiliate, affiliate_rate_bp, affiliate_base
FROM tickets
WHERE day_id = $1
AND affiliate IS NOT NULL
AND affiliate_rate_bp > 0
ORDER BY ticket_id
Berechnung: amount = (affiliate_base * affiliate_rate_bp) / 10000
Aggregation: Gruppiert nach Affiliate-Adresse für Merkle-Leaf.
Schritt 3: Compute Settlement¶
Datei:
cli/run.ts(591 Zeilen)
Der Kernschritt - berechnet die gesamte Gewinnverteilung:
- Lädt Inputs-Artefakt aus DB (Source of Truth)
- Liest On-Chain-State via ChainReader:
potAcc[5]- Regular PotsrolloverAcc[5]- Rollover-AkkumulatorenjackpotReserveAcc[5]- Jackpot-ReservenfeeAcc(dayId)- Operator-FeesjackpotStates[5]- No-Hit-Streaks- Ruft
computeSettlementForDay()auf - Baut Merkle-Trees (Winners + Affiliates)
- Berechnet Charity-Anteil
- Schreibt Artefakte (DB + Filesystem)
- Erstellt DB-Einträge:
settlement_runs(Status: dry_run)settlement_proofs(Merkle-Daten)settlement_winner_payouts(pro Gewinner)settlement_affiliate_payouts(pro Affiliate)daily_prize_breakdown(für Dashboard)
Idempotenz: run_hash = sha256(commitBundle) - bei gleichem Hash wird der existierende Run wiederverwendet.
Schritt 4: Approve Settlement¶
Datei:
cli/approve_settlement.ts(122 Zeilen)
Führt 7 Validierungs-Gates durch (siehe Approval Gateway).
Schritt 5: Commit On-Chain¶
Datei:
cli/commit_from_db.ts(110 Zeilen)
Sendet die Transaktion an SettlementV4 (siehe Blockchain Commit).
Schritte 6-8: Non-Fatal Workers¶
Diese Schritte sind non-fatal - ein Fehler blockiert nicht die Pipeline:
// settle.ts:88-111
// Schritt 6: Pot-Sync
try {
await step("updatePotSizes", () => execScript("updatePotSizes.ts"));
} catch (e) {
console.warn("Pot sync failed (non-fatal):", e.message);
}
// Schritt 7: Global Stats
await execScript("refresh-global-stats.ts");
// Schritt 8: Winner Notifications
try {
await notifyWinners(dayId);
} catch (e) {
console.warn("Notifications failed (non-fatal):", e.message);
}
Pipeline-Tracking¶
Die settlement_pipeline_runs-Tabelle trackt jeden Schritt:
settlement_pipeline_runs (
day_id INT PRIMARY KEY,
started_at TIMESTAMP,
finished_at TIMESTAMP,
status VARCHAR, -- RUNNING / DONE / FAILED
export_ok BOOLEAN,
affiliate_ok BOOLEAN,
compute_ok BOOLEAN,
approve_ok BOOLEAN,
commit_ok BOOLEAN,
stats_ok BOOLEAN
)
Fehlerbehandlung¶
| Szenario | Verhalten |
|---|---|
| Schritt 1-5 fehlschlägt | status='FAILED', Exit Code 1 |
| Schritt 6-8 fehlschlägt | Warning geloggt, Pipeline als DONE |
| Bereits gesettled | daySettled(dayId) prüft On-Chain → Skip |
| RPC nicht erreichbar | waitUntilDayIsReady() wartet endlos |
| Doppelter Start | Pipeline-Status RUNNING → neuer Start schlägt fehl |
Cron-Wrapper¶
# cli/cron-settle.sh
#!/bin/bash
set -e
cd /root/workspace/lotto-platform/bo-engine
LOG="logs/settlement/settle-$(date +%Y-%m-%d).log"
mkdir -p logs/settlement
npx ts-node --files cli/settle.ts >> "$LOG" 2>&1
# Log-Rotation: 30 Tage
find logs/settlement -name "*.log" -mtime +30 -delete
Zeitlicher Ablauf¶
00:00 UTC Cron startet settle.ts
00:00-00:15 waitUntilDayIsReady() (wartet auf finalisierte Blöcke)
00:15-00:16 Export Inputs + Affiliates
00:16-00:17 Compute Settlement + Merkle-Trees
00:17 Approve Settlement (7 Gates)
00:17-00:18 Commit On-Chain (TX + Confirmation)
00:18-00:19 Pot-Sync + Stats + Notifications
~00:20 UTC Pipeline DONE
Watchdog Grace Period: Bis 00:20 UTC werden keine Settlement-Alarme ausgelöst.