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()¶
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¶
fulfillRandomWords() (Chainlink Callback)¶
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);
Chainlink VRF V2+ Konfiguration¶
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.