Zum Inhalt

10 - VRFReceiverV3

Chainlink VRF V2+ Integration, PUSH-Modell, DoS-Schutz


Übersicht

VRFReceiverV3 ist der Chainlink VRF Consumer, der verifizierbare Zufallszahlen anfordert und den Seed an den GameManager pusht. Das PUSH-Modell mit try/catch garantiert, dass der VRF-Callback niemals revertiert.

Datei: contracts/contracts/VRFReceiverV3.sol


State Variables

bytes32 public keyHash;              // Gas Lane Identifier
uint256 public subscriptionId;       // Chainlink Subscription
uint256 public lastRequestId;        // Letzte Request ID
uint256 public lastVrfSeed;          // Aktuellster Seed
uint256 public fulfilledTimestamp;    // Wann der Seed empfangen wurde

address public operator;             // Triggert VRF Requests
IGameManager public gameManager;     // PUSH-Ziel

Request Flow

requestVrfSeed()

function requestVrfSeed() external onlyOperator returns (uint256 requestId)

Ablauf: 1. Operator (Bot oder manuell) triggert Request 2. Contract sendet Request an Chainlink VRF Coordinator 3. Chainlink wartet requestConfirmations Blöcke 4. Chainlink ruft fulfillRandomWords() Callback auf

Parameter:

Parameter Wert Beschreibung
keyHash Netzwerk-spezifisch Gas Lane (z.B. 500 gwei)
requestConfirmations 3 Blöcke bis Fulfillment
callbackGasLimit 500000 Max Gas für Callback
numWords 1 Eine Zufallszahl
nativePayment false LINK-basierte Zahlung

Fulfillment Flow

function fulfillRandomWords(
    uint256 requestId,
    uint256[] memory randomWords
) internal override

Ablauf:

1. Validierung

require(requestId == lastRequestId, "Unknown request");
require(randomWords.length > 0, "Empty response");

2. Seed speichern

lastVrfSeed = randomWords[0];
fulfilledTimestamp = block.timestamp;
emit VrfSeedReceived(requestId, lastVrfSeed);

3. PUSH an GameManager (mit DoS-Schutz)

try gameManager.setVrfSeed(lastVrfSeed) {
    emit VrfSeedPushed(requestId, lastVrfSeed);
} catch (bytes memory reason) {
    // Log failure, aber KEIN REVERT
    emit VrfSeedPushFailed(requestId, lastVrfSeed, reason);
}


DoS-Schutz Mechanismus

Problem

Wenn gameManager.setVrfSeed() revertiert, würde ohne try/catch der gesamte Chainlink Callback reverieren. Chainlink würde den Callback als fehlgeschlagen markieren und den Seed verwerfen.

Lösung: try/catch

Chainlink VRF Coordinator
VRFReceiverV3.fulfillRandomWords()
        ├── lastVrfSeed = seed  ✅ (immer gespeichert)
        ├── try gameManager.setVrfSeed(seed)
        │   ├── Erfolg → VrfSeedPushed Event
        │   └── Fehler → VrfSeedPushFailed Event (kein Revert!)
        └── return  ✅ (Callback immer erfolgreich)

Fallback

Falls der Push fehlschlägt: 1. Seed ist trotzdem in lastVrfSeed gespeichert 2. Operator kann manuell gameManager.setVrfSeed(lastVrfSeed) aufrufen 3. SAFE kann gameManager.setVrfSeed() als Owner aufrufen 4. Pool 0 funktioniert weiterhin (nutzt prevrandao, kein VRF nötig)


Access Control

Funktion Owner (SAFE) Operator Chainlink
requestVrfSeed - -
fulfillRandomWords - - ✅ (intern)
setGameManager - -
setOperator - -
setKeyHash - -
setSubscriptionId - -

Events

event VrfRequested(uint256 requestId);
event VrfSeedReceived(uint256 requestId, uint256 seed);
event VrfSeedPushed(uint256 requestId, uint256 seed);
event VrfSeedPushFailed(uint256 requestId, uint256 seed, bytes reason);

Subscription Management

  • Subscription muss mit LINK funded sein
  • VRFReceiverV3 muss als Consumer registriert sein
  • Monitoring via vrf.chain.link

Netzwerk-Adressen

Netzwerk VRF Coordinator
Arbitrum Sepolia Chainlink Sepolia Coordinator
Arbitrum One Chainlink Mainnet Coordinator

Timing & Lifecycle

Täglich:
    1. Operator ruft requestVrfSeed() auf
    2. ~3 Blöcke warten (~6 Sekunden auf Arbitrum)
    3. Chainlink liefert Seed via Callback
    4. Seed wird automatisch an GameManager gepusht
    5. Alle Ticket-Käufe an diesem Tag nutzen diesen Seed

Seed-Rotation:
    - Pro Tag ein neuer VRF Seed (typisch)
    - Seed bleibt gültig bis zum nächsten Request
    - Pool 0 braucht keinen VRF Seed (prevrandao)

Migration Support

// Neuen GameManager setzen (z.B. bei V3 → V4 Migration)
vrfReceiver.setGameManager(newGameManager);

// Letzten Seed manuell an neuen GameManager pushen
newGameManager.setVrfSeed(vrfReceiver.lastVrfSeed());

Audit-Hinweise

try/catch ist BEABSICHTIGT

Der try/catch im Fulfillment ist ein bewusstes Design-Pattern und kein Bug. Er verhindert, dass ein fehlerhafter GameManager den VRF-Prozess blockiert.

Seed ist permanent

Auch bei fehlgeschlagenem Push ist der Seed in lastVrfSeed gespeichert und kann jederzeit manuell weitergeleitet werden.

LINK-Kosten

Jeder VRF Request kostet LINK Token. Die Subscription muss regelmäßig aufgeladen werden. Monitoring via Chainlink Dashboard.