Seed-Rotation¶
Datei:
workers/refreshSeeds.ts(264 Zeilen) Typ: Daemon (kontinuierlicher Loop alle 30 Sekunden) PID-Lock:logs/refresh-seeds.pid
Übersicht¶
Der Seed-Rotation-Daemon hält die RNG-Quellen des GameManager-Contracts aktuell. Zwei unabhängige Seed-Typen werden rotiert:
refreshSeeds.ts (30s Loop)
├── VRF-Seed (Chainlink VRF V2+)
│ └── Trigger: Ticket-Counter >= Schwellwert
│
└── Drand-Seed (lokale Entropie)
└── Trigger: Random-Intervall (10-45 Minuten)
VRF-Seed-Rotation¶
Trigger-Bedingung¶
// refreshSeeds.ts:101-110
const counter = await gameManager.vrfTicketCounter();
const threshold = currentThreshold; // Random 20-80
if (counter >= threshold) {
await refreshVrfSeed();
currentThreshold = randomInt(20, 80); // Neuen Threshold würfeln
}
Logik: Nach jeweils 20-80 Tickets (zufällig gewählt) wird ein neuer VRF-Seed angefordert.
VRF-Refresh-Ablauf¶
// refreshSeeds.ts:112-150
async function refreshVrfSeed() {
// 1. VRF-Request senden
const tx = await vrfReceiver.requestVrfSeed();
await tx.wait();
// 2. Auf Chainlink Fulfillment warten (max 90 Sekunden)
const VRF_TIMEOUT = 90_000;
const VRF_POLL = 3_000;
const startTime = Date.now();
while (Date.now() - startTime < VRF_TIMEOUT) {
const fulfilled = await vrfReceiver.fulfilledTimestamp();
if (fulfilled > previousFulfilled) {
// Seed erfolgreich rotiert
break;
}
await sleep(VRF_POLL);
}
// 3. DB-Logging
await pg.query(
`INSERT INTO seed_updates (seed_type, seed_value, trigger_info, tx_hash)
VALUES ('vrf', $1, $2, $3)`,
[newSeed, `counter=${counter}, threshold=${threshold}`, tx.hash]
);
}
VRF-Parameter¶
| Parameter | Wert |
|---|---|
| Fulfillment Timeout | 90 Sekunden |
| Poll-Intervall | 3 Sekunden |
| Threshold-Range | 20-80 Tickets |
| Contract | VRFReceiverV3 |
| Provider | Chainlink VRF V2+ |
Drand-Seed-Rotation¶
Trigger-Bedingung¶
// refreshSeeds.ts:156-165
const nextRotation = lastDrandRotation + currentInterval;
if (Date.now() >= nextRotation) {
await rotateDrandSeed();
currentInterval = randomInt(10, 45) * 60_000; // 10-45 Minuten
}
Drand-Rotation-Ablauf¶
// refreshSeeds.ts:167-192
async function rotateDrandSeed() {
// 1. Seed generieren (lokale Entropie + Timestamp)
const entropy = crypto.randomBytes(32);
const seed = keccak256(
concat([entropy, toBeHex(Date.now())])
);
// 2. On-Chain setzen
const tx = await gameManager.setDrandSeed(seed);
await tx.wait();
// 3. DB-Logging
await pg.query(
`INSERT INTO seed_updates (seed_type, seed_value, trigger_info, tx_hash)
VALUES ('drand', $1, $2, $3)`,
[seed, `interval=${currentInterval}ms`, tx.hash]
);
}
Drand-Parameter¶
| Parameter | Wert |
|---|---|
| Intervall-Range | 10-45 Minuten (zufällig) |
| Entropie-Quelle | crypto.randomBytes(32) + Timestamp |
| Hash-Funktion | keccak256 |
| Contract | GameManagerV3 |
Daemon-Management¶
PID-Lock¶
# cron-refresh-seeds.sh
PID_FILE="logs/refresh-seeds.pid"
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
exit 0 # Bereits laufend
fi
npx ts-node --files workers/refreshSeeds.ts &
echo $! > "$PID_FILE"
- Cron prüft jede Minute ob der Daemon läuft
- Bei Absturz: Automatischer Neustart in der nächsten Minute
- PID-File verhindert Mehrfachstart
Loop-Struktur¶
// refreshSeeds.ts:198-254
async function main() {
while (true) {
try {
await checkVrf();
await checkDrand();
} catch (e) {
console.error("Loop error:", e.message);
// Weiter - kein Crash
}
await sleep(30_000); // 30 Sekunden
}
}
Error Handling¶
- Einzelne Fehler (VRF-Timeout, TX-Revert) werden geloggt
- Loop läuft weiter - kein Crash bei Einzelfehlern
- VRF-Timeout: Warning, nächster Versuch im nächsten Loop
DB-Logging¶
seed_updates (
id SERIAL PRIMARY KEY,
seed_type VARCHAR, -- 'vrf' / 'drand'
seed_value VARCHAR, -- Hex-Wert des Seeds
trigger_info TEXT, -- z.B. "counter=45, threshold=42"
tx_hash VARCHAR, -- Blockchain-TX
created_at TIMESTAMP DEFAULT NOW()
)
Sicherheitsrelevanz¶
Die Seed-Rotation ist sicherheitskritisch für die Fairness:
- VRF-Seed: Chainlink liefert nachweislich zufällige Werte (verifizierbar on-chain)
- Drand-Seed: Lokale Entropie als zusätzliche Randomness-Quelle
- Kombination: GameManager nutzt beide Seeds für RNG:
keccak256(vrfSeed + drandSeed + nonce) - Rotation: Unvorhersehbare Intervalle verhindern Manipulation
- Watchdog-Check C5: Alarm wenn VRF-Seed = 0 (keine Tickets möglich)