Vue d'ensemble
Le dossier backend/src/blockchain/ contient tout le code qui relie le backend Express aux deux contrats Solidity BlockLucky (loterie) et BlockLuckyPOAP (badges de participation). Il ne contient aucune route HTTP : les routes de backend/src/api/lottery/lottery.services.ts, backend/src/api/admin/admin.services.ts et backend/src/services/blockchain.service.ts appellent ces modules quand BLOCKCHAIN_MODE=onchain.
Le principe directeur est simple : la chaîne fait foi. Le backend signe et envoie les transactions, puis il lit les événements émis par le contrat pour mettre à jour PostgreSQL. La base est une projection de la chaîne, enrichie de données qui n'existent pas sur la chaîne (comptes, EtherPoints, récompenses).
Fichiers du dossier
| Fichier | Rôle | Page détaillée |
|---|---|---|
chain.client.ts |
Fournisseur JSON-RPC unique, RPC de secours, compte opérateur avec gestion des nonces, instances des contrats, liens vers l'explorateur | Client RPC et compte opérateur |
wallet.service.ts |
Création et chiffrement des portefeuilles intégrés, signataires, dotation de bienvenue, lecture des soldes | Portefeuilles intégrés |
lottery.gateway.ts |
Lecture des tours, achat, tirage, annulation, versement, pause, reversement de la commission, badges ; traduction des erreurs du contrat en erreurs HTTP | Passerelle du contrat et erreurs |
lottery.indexer.ts |
Lecture des journaux du contrat bloc par bloc et projection en base (tours, tickets, points) | Indexeur d'événements |
lottery.keeper.ts |
Automate qui lance le tirage ou clôture un tour expiré, et démarrage périodique de l'indexeur et du keeper | Keeper du tirage |
chain-actions.ts |
File BullMQ chain-actions : frappe des POAP, versement de la cagnotte, remboursements ; décodage des erreurs personnalisées Solidity |
File d'actions on-chain |
poap.metadata.ts |
Métadonnées ERC-721 et visuel SVG des badges servis par l'API | Métadonnées POAP |
abi/BlockLucky.json, abi/BlockLuckyPOAP.json |
ABI des contrats, exportées depuis smart-contract/ par scripts/export-abi.mjs et vérifiées en CI (node scripts/export-abi.mjs --check) |
Smart contracts |
Deux fichiers hors du dossier complètent l'ensemble : backend/src/socket/event-bridge.ts, qui relaie vers Socket.IO les événements produits par le process worker (Relais temps réel), et backend/src/utils/crypto.util.ts, qui chiffre les clés privées.
Composants et dépendances
Le diagramme suivant montre qui importe qui. Les flèches vont de l'appelant vers le module appelé.
flowchart TD
services["Services API (lottery, admin, blockchain)"] --> gateway["lottery.gateway.ts"]
services --> wallet["wallet.service.ts"]
gateway --> wallet
gateway --> indexer["lottery.indexer.ts"]
gateway --> actions["chain-actions.ts"]
keeper["lottery.keeper.ts"] --> indexer
keeper --> actions
indexer --> actions
indexer --> ledger["ledger.service.ts"]
indexer --> bridge["socket/event-bridge.ts"]
actions --> wallet
wallet --> crypto["utils/crypto.util.ts"]
gateway --> client["chain.client.ts"]
wallet --> client
indexer --> client
keeper --> client
actions --> client
client --> rpc["Nœud JSON-RPC"]Points notables :
chain.client.tsest le seul module qui crée unJsonRpcProvider. Tous les autres passent par le singletonchainClient.chain-actions.tsexporte aussidecodeContractError, utilisé par la passerelle et le keeper. C'est pour cette raison que la passerelle importechain-actions.ts.- Le module historique
backend/src/services/blockchain.service.tscrée encore ses propres fournisseurs RPC qui ne servent à rien en mode onchain. En mode simulé, il produit des hachages de transaction aléatoires. Voir Mode onchain ou simulé.
Où s'exécute chaque composant
Le backend peut tourner en un seul process ou en deux (API et worker). Les composants ne démarrent pas au même endroit.
| Composant | Process API (src/server.ts) |
Process worker (src/worker.ts) |
|---|---|---|
| Passerelle, portefeuilles (appelés par les requêtes HTTP) | oui | non |
Indexeur périodique et keeper (startChainServices) |
seulement si RUN_WORKERS=true |
oui |
Worker BullMQ chain-actions (startWorkers) |
seulement si RUN_WORKERS=true |
oui |
Abonné du relais Socket.IO (startSocketEventBridge) |
oui | non |
Dans docker-compose.yml, le service backend tourne avec RUN_WORKERS: "false" et le service worker lance dist/src/worker.js : l'indexeur, le keeper et la file tournent donc dans le worker. En développement (npm run dev), RUN_WORKERS vaut true par défaut et tout tourne dans le même process.
startChainServices renvoie null en mode simulé : aucun minuteur n'est créé.
Flux d'un achat de ticket
Ce flux résume l'enchaînement des modules pour POST /api/v1/lottery/buy en mode onchain.
sequenceDiagram
participant API as "lottery.services"
participant GW as "lottery.gateway"
participant WS as "wallet.service"
participant C as "Contrat BlockLucky"
participant IDX as "lottery.indexer"
participant Q as "File chain-actions"
API->>GW: prepareWallet(user)
GW->>WS: ensureWallet puis grantWelcomeEthers
API->>GW: buyTickets(buyer, quantity)
GW->>C: getRound(currentRoundId)
GW->>C: buyTickets(quantity) signé par le portefeuille du citoyen
C-->>GW: reçu avec l'événement TicketsPurchased
GW->>IDX: processReceipt(receipt)
IDX->>IDX: tickets, cagnotte et EtherPoints en base
IDX->>Q: MINT_BADGE
GW-->>API: reçu et identifiants des ticketsL'achat est donc indexé immédiatement à partir du reçu, sans attendre le passage de l'indexeur périodique. Quand l'indexeur périodique relit plus tard le même bloc, il retrouve les tickets déjà présents et ne crée rien (voir Idempotence).
Événements produits par la chaîne et suites données
| Événement du contrat | Effet en base | Suite automatique |
|---|---|---|
RoundOpened |
Création ou mise à jour du tour | aucune |
TicketsPurchased |
Tickets, cagnotte, EtherPoints | Job MINT_BADGE, événement Socket.IO points:awarded |
DrawRequested |
Tour LOCKED, identifiant de requête VRF |
aucune (l'oracle répond) |
WinnerPicked |
Tour COMPLETED, gagnant, cagnotte nette |
Événement draw:winner, job CLAIM_PRIZE |
PrizeClaimed |
Date de versement | aucune |
RoundCancelled |
Tour CANCELLED |
Un job CLAIM_REFUND par payeur distinct |
Les autres événements (RefundClaimed, FeesWithdrawn, TreasuryUpdated, RoundConfigUpdated, VrfConfigUpdated, ceux du contrat POAP) sont ignorés par l'indexeur.
Ce qui n'est pas couvert
- Aucun test unitaire ne cible directement les modules de
src/blockchain/. Ils sont exercés par le test d'intégration sur un nœud Hardhat (voir Tests sur une vraie chaîne). - L'indexeur n'a pas de verrou entre process : si l'API et le worker exécutent tous deux
startChainServices, les deux indexent en parallèle. L'idempotence protège les tickets, mais les événements Socket.IO deWinnerPickedpeuvent être émis deux fois. - La bascule vers le RPC de secours n'est pas atteignable en mode onchain (voir RPC de secours).