Disjoncteur
Le disjoncteur est l'arrêt d'urgence de la loterie. Un administrateur de la Mairie le déclenche depuis la page sécurité de la console : le backend appelle pause() sur le contrat BlockLucky, ce qui bloque immédiatement les achats et les demandes de tirage, pour tous les clients, y compris ceux qui appelleraient le contrat sans passer par BlockLucky. La reprise passe par unpause(). Chaque action est inscrite au journal d'audit.
Diagramme de séquence
sequenceDiagram autonumber participant M as Console Mairie participant API as API admin participant RB as requireRoles participant AS as AdminService participant BS as BlockchainService participant G as LotteryGateway participant BL as Contrat BlockLucky participant DB as PostgreSQL M->>API: POST /admin/circuit-breaker paused true, reason API->>RB: authenticate puis requireRoles ADMIN RB->>DB: rôle, deletedAt, isTwoFactorEnabled API->>AS: toggleCircuitBreaker(adminId, paused, reason) AS->>BS: setCircuitBreaker(paused) BS->>G: setPaused(true) G->>BL: pause par le compte opérateur, PAUSER_ROLE BL-->>G: reçu avec Paused G-->>BS: receipt AS->>DB: Analytics ADMIN_AUDIT:CIRCUIT_BREAKER_PAUSE AS-->>M: 200 paused, txHash, reason Note over BL: buyTickets, buyTicketsFor, requestDraw revert EnforcedPause
Pas à pas
Interface. La page frontend-web/src/app/mairie/securite/page.tsx appelle la mutation toggleCircuitBreaker de frontend-web/src/store/api/adminApi.ts.
Route. POST /api/v1/admin/circuit-breaker dans backend/src/api/admin/admin.routes.ts. Tout le routeur admin est protégé par router.use(authenticate, requireRoles('ADMIN')). Puis validate(circuitBreakerSchema) (backend/src/api/admin/admin.validators.ts) : paused booléen obligatoire, reason facultatif d'au moins 5 caractères.
Contrôle des droits. requireRoles (backend/src/middlewares/rbac.middleware.ts) ne se fie pas seulement au rôle du jeton : il relit en base le rôle, deletedAt et isTwoFactorEnabled. Un compte rétrogradé ou supprimé depuis sa connexion est refusé, et un administrateur sans 2FA est refusé tant que ADMIN_MFA_REQUIRED=true (valeur imposée en production).
Service. adminController.toggleCircuitBreaker appelle adminService.toggleCircuitBreaker (backend/src/api/admin/admin.services.ts), qui :
- Appelle
blockchainService.setCircuitBreaker(paused)(backend/src/services/blockchain.service.ts). - En mode
onchain, celui-ci appellelotteryGateway.setPaused(paused)(backend/src/blockchain/lottery.gateway.ts), qui envoiepause()ouunpause()avec le compte opérateur et attend le reçu parsendAndIndex. Le changement est journalisé en avertissement avec le hash. - Enregistre l'action par
adminRepository.recordAdminAuditLog(backend/src/api/admin/admin.repo.ts) : une ligneAnalyticsd'eventTypeADMIN_AUDIT:CIRCUIT_BREAKER_PAUSEouADMIN_AUDIT:CIRCUIT_BREAKER_RESUME, avecpaused,reason(« Action manuelle de sécurité Mairie / DSI » par défaut) ettxHash. - Répond 200 avec
paused,txHash,timestampetreason. Le journal est consultable parGET /api/v1/admin/audit-logs.
Contrat. pause() et unpause() (smart-contract/contracts/BlockLucky.sol) exigent PAUSER_ROLE, accordé au compte opérateur au déploiement. Elles émettent les événements Paused et Unpaused d'OpenZeppelin, qui ne sont pas indexés par le backend.
Effets de la pause
| Fonction du contrat | Pendant la pause |
|---|---|
buyTickets, buyTicketsFor |
Refusées (EnforcedPause) |
requestDraw, et donc performUpkeep pour un tirage |
Refusées |
checkUpkeep |
Renvoie false |
fulfillRandomWords |
Autorisée : un tirage déjà demandé se termine et désigne un gagnant |
claimPrize, claimRefund |
Autorisées : les gagnants et les participants d'un tour annulé récupèrent leurs fonds |
cancelRound, closeExpiredRound, retryDraw, withdrawFees |
Autorisées |
Côté backend :
- Le keeper ne fait rien tant que
paused()est vrai (backend/src/blockchain/lottery.keeper.ts), y compris pour clôturer un tour expiré. - Un achat renvoie 503 avec
errors.contractError: 'EnforcedPause'eterrors.circuitBreaker: true. Le frontend reconnaît ce drapeau (isCircuitBreakerErrordansfrontend-web/src/hooks/useBlockLuckyWallet.ts). GET /api/v1/lottery/statusrenvoieguarantees.pausedetnetwork.isPaused, lus dans le contrat.- L'indexeur et la file
chain-actionscontinuent : les cagnottes et remboursements en attente sont versés.
Erreurs possibles
| Code | Cause |
|---|---|
| 400 | paused absent ou non booléen, reason de moins de 5 caractères |
| 401 | Jeton absent ou invalide |
| 403 | Rôle ADMIN absent, droits modifiés, administrateur sans 2FA ; ou AccessControlUnauthorizedAccount si le compte opérateur n'a pas PAUSER_ROLE |
| 503 | pause demandée alors que le contrat est déjà en pause : le contrat répond EnforcedPause, traduit par le message générique « Le Smart Contract est temporairement en pause de sécurité » ; ou réseau injoignable |
| 502 | unpause demandée alors que le contrat n'est pas en pause : ExpectedPause n'est pas dans la table de traduction, d'où le message de repli « Le changement d'état du disjoncteur a échoué » |
| 503 | Vérification des droits impossible (base indisponible) |
Idempotence
La route n'a pas de middleware idempotency. Le contrat rend l'opération idempotente dans les faits : une seconde pause ou une seconde reprise échoue sans rien changer, avec les codes ci-dessus. Le journal d'audit n'est écrit qu'après une transaction réussie : une tentative refusée n'y figure pas.
Danger
isCircuitBreakerPaused renvoie false si la lecture de paused() échoue (nœud injoignable). Pendant une panne RPC, le tableau de bord de la Mairie (GET /admin/dashboard, champ network.isPaused) peut donc afficher le contrat comme actif alors qu'il est en pause. Le contrat, lui, reste en pause. GET /lottery/status lit paused() sans ce repli et échoue dans la même situation.
En mode simulé
setCircuitBreaker écrit true ou false dans la clé Redis blockchain:circuit_breaker et dans une variable du process, puis renvoie un hash aléatoire. relayTicketPurchase lit cet état et refuse l'achat avec un 503 circuitBreaker: true. Le tirage simulé, lui, ne consulte pas le disjoncteur.
Voir aussi
- Sécurité du contrat : rôles et pause côté Solidity.
- Réponse aux incidents : quand déclencher le disjoncteur.
- Administration Mairie : les autres actions de la console.