Zum Inhalt

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:

  1. VRF-Seed: Chainlink liefert nachweislich zufällige Werte (verifizierbar on-chain)
  2. Drand-Seed: Lokale Entropie als zusätzliche Randomness-Quelle
  3. Kombination: GameManager nutzt beide Seeds für RNG: keccak256(vrfSeed + drandSeed + nonce)
  4. Rotation: Unvorhersehbare Intervalle verhindern Manipulation
  5. Watchdog-Check C5: Alarm wenn VRF-Seed = 0 (keine Tickets möglich)