Skip to content

blockchain

ISSUE #8 : BLOCKCHAIN & TRAÇABILITÉ

🎯 OBJECTIF

Implémenter un système de blockchain simplifié pour garantir la traçabilité et l'intégrité des actions.

📖 CONTEXTE DU PROJET

La blockchain est un élément clé de SmartAudit pour garantir la transparence et lutter contre la corruption. Chaque action importante est enregistrée dans une chaîne de blocs immuable avec hash cryptographique.

Principe de Fonctionnement

  1. Hash SHA-256 : Chaque bloc a un hash unique
  2. Chaînage : Chaque bloc contient le hash du précédent
  3. Immuabilité : Modification = rupture de chaîne
  4. Vérification : Validation automatique de l'intégrité

Actions Traçées

  1. ALERT_CREATED : Création d'alerte IA
  2. AUDIT_CREATED : Création d'audit
  3. AUDIT_COMPLETED : Finalisation audit
  4. ALERT_ASSIGNED : Assignation alerte
  5. ALERT_RESOLVED : Résolution alerte
  6. COMPANY_REGISTERED : Inscription entreprise
  7. DOCUMENT_UPLOADED : Upload document
  8. REPORT_GENERATED : Génération rapport
  9. USER_LOGIN : Connexion utilisateur
  10. SYSTEM_BACKUP : Sauvegarde système

Avantages

  • Transparence : Toutes les actions visibles
  • Anti-corruption : Impossible de modifier l'historique
  • Audit trail : Traçabilité complète
  • Confiance : Intégrité garantie

🛠️ TÂCHES À IMPLÉMENTER

1. Service Blockchain

// Fichier : service/BlockchainService.java
@Service
@Transactional
public class BlockchainService {
    
    @Autowired
    private BlockchainEntryRepository blockchainRepository;
    
    @Autowired
    private UserAccountRepository userAccountRepository;
    
    public BlockchainEntry recordAction(String actionType, Object entity, UserAccount user) {
        try {
            // 1. Récupérer le dernier bloc
            BlockchainEntry lastBlock = getLastBlock();
            
            // 2. Calculer le numéro du nouveau bloc
            Long blockNumber = lastBlock != null ? lastBlock.getBlockNumber() + 1 : 1L;
            
            // 3. Préparer les données du bloc
            String data = prepareBlockData(entity, actionType);
            String userIdentity = getUserIdentity(user);
            String previousHash = lastBlock != null ? lastBlock.getCurrentHash() : "0";
            
            // 4. Créer le nouveau bloc
            BlockchainEntry newBlock = new BlockchainEntry();
            newBlock.setPreviousHash(previousHash);
            newBlock.setActionType(actionType);
            newBlock.setData(data);
            newBlock.setUserIdentity(userIdentity);
            newBlock.setBlockNumber(blockNumber);
            newBlock.setTimestamp(LocalDateTime.now());
            newBlock.setEntityType(getEntityType(entity));
            newBlock.setEntityId(getEntityId(entity));
            
            // 5. Calculer le hash du bloc
            newBlock.setCurrentHash(calculateHash(newBlock));
            
            // 6. Sauvegarder le bloc
            BlockchainEntry savedBlock = blockchainRepository.save(newBlock);
            
            // 7. Vérifier l'intégrité de la chaîne
            validateBlockchainIntegrity();
            
            log.info("Bloc blockchain créé: {} - {} par {}", blockNumber, actionType, userIdentity);
            
            return savedBlock;
            
        } catch (Exception e) {
            log.error("Erreur création bloc blockchain: {}", e.getMessage());
            throw new BlockchainException("Erreur enregistrement action blockchain", e);
        }
    }
    
    public String getAuditHash(Audit audit) {
        // Récupérer tous les blocs liés à cet audit
        List<BlockchainEntry> auditBlocks = blockchainRepository.findByEntityTypeAndEntityId("Audit", audit.getId());
        
        // Créer un hash composite de tous les blocs
        StringBuilder compositeData = new StringBuilder();
        for (BlockchainEntry block : auditBlocks) {
            compositeData.append(block.getCurrentHash());
        }
        
        return calculateSHA256(compositeData.toString());
    }
    
    public boolean validateBlockchainIntegrity() {
        try {
            List<BlockchainEntry> allBlocks = blockchainRepository.findAllByOrderByBlockNumberAsc();
            
            for (int i = 1; i < allBlocks.size(); i++) {
                BlockchainEntry currentBlock = allBlocks.get(i);
                BlockchainEntry previousBlock = allBlocks.get(i - 1);
                
                // Vérifier que le hash précédent correspond
                if (!currentBlock.getPreviousHash().equals(previousBlock.getCurrentHash())) {
                    log.error("Intégrité blockchain compromise au bloc {}", currentBlock.getBlockNumber());
                    return false;
                }
                
                // Vérifier que le hash actuel est correct
                String calculatedHash = calculateHash(currentBlock);
                if (!currentBlock.getCurrentHash().equals(calculatedHash)) {
                    log.error("Hash invalide au bloc {}", currentBlock.getBlockNumber());
                    return false;
                }
            }
            
            log.info("Intégrité blockchain validée: {} blocs vérifiés", allBlocks.size());
            return true;
            
        } catch (Exception e) {
            log.error("Erreur validation intégrité blockchain: {}", e.getMessage());
            return false;
        }
    }
    
    public List<BlockchainEntry> getBlockchainHistory(String entityType, Long entityId) {
        return blockchainRepository.findByEntityTypeAndEntityIdOrderByBlockNumberAsc(entityType, entityId);
    }
    
    public BlockchainStatistics getBlockchainStatistics() {
        long totalBlocks = blockchainRepository.count();
        long uniqueUsers = blockchainRepository.countDistinctUserIdentity();
        long actionsToday = blockchainRepository.countByTimestampAfter(LocalDateTime.now().toLocalDate().atStartOfDay());
        
        Map<String, Long> actionsByType = blockchainRepository.countByActionType();
        
        return BlockchainStatistics.builder()
            .totalBlocks(totalBlocks)
            .uniqueUsers(uniqueUsers)
            .actionsToday(actionsToday)
            .actionsByType(actionsByType)
            .isIntegrityValid(validateBlockchainIntegrity())
            .build();
    }
    
    private String calculateHash(BlockchainEntry block) {
        try {
            String dataToHash = block.getPreviousHash() + 
                               block.getActionType() + 
                               block.getData() + 
                               block.getUserIdentity() + 
                               block.getTimestamp().toString() + 
                               block.getBlockNumber();
            
            return calculateSHA256(dataToHash);
            
        } catch (Exception e) {
            throw new BlockchainException("Erreur calcul hash", e);
        }
    }
    
    private String calculateSHA256(String input) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(input.getBytes("UTF-8"));
            
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            
            return hexString.toString();
            
        } catch (Exception e) {
            throw new BlockchainException("Erreur calcul SHA-256", e);
        }
    }
    
    private String prepareBlockData(Object entity, String actionType) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            
            // Créer un objet de données simplifié
            Map<String, Object> data = new HashMap<>();
            data.put("actionType", actionType);
            data.put("timestamp", LocalDateTime.now());
            
            // Ajouter des données spécifiques selon le type d'entité
            if (entity instanceof Alert) {
                Alert alert = (Alert) entity;
                data.put("alertId", alert.getId());
                data.put("alertType", alert.getType());
                data.put("companyIfu", alert.getCompany().getIfu());
                data.put("priority", alert.getPriority());
            } else if (entity instanceof Audit) {
                Audit audit = (Audit) entity;
                data.put("auditId", audit.getId());
                data.put("reportNumber", audit.getReportNumber());
                data.put("companyIfu", audit.getCompany().getIfu());
                data.put("status", audit.getStatus());
            } else if (entity instanceof Company) {
                Company company = (Company) entity;
                data.put("companyId", company.getId());
                data.put("ifu", company.getIfu());
                data.put("companyName", company.getCompanyName());
                data.put("status", company.getStatus());
            }
            
            return mapper.writeValueAsString(data);
            
        } catch (Exception e) {
            log.error("Erreur préparation données bloc: {}", e.getMessage());
            return "{\"error\": \"Données non disponibles\"}";
        }
    }
    
    private String getUserIdentity(UserAccount user) {
        return user.getUsername() + " (" + user.getRole() + ")";
    }
    
    private String getEntityType(Object entity) {
        return entity.getClass().getSimpleName();
    }
    
    private Long getEntityId(Object entity) {
        try {
            Method getIdMethod = entity.getClass().getMethod("getId");
            return (Long) getIdMethod.invoke(entity);
        } catch (Exception e) {
            return null;
        }
    }
    
    private BlockchainEntry getLastBlock() {
        return blockchainRepository.findTopByOrderByBlockNumberDesc();
    }
}

2. Controllers REST

// Fichier : controller/BlockchainController.java
@RestController
@RequestMapping("/api/blockchain")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPERVISEUR')")
public class BlockchainController {
    
    @Autowired
    private BlockchainService blockchainService;
    
    @GetMapping("/history/{entityType}/{entityId}")
    public ResponseEntity<List<BlockchainEntryDTO>> getEntityHistory(
            @PathVariable String entityType, 
            @PathVariable Long entityId) {
        
        List<BlockchainEntry> entries = blockchainService.getBlockchainHistory(entityType, entityId);
        
        List<BlockchainEntryDTO> entryDTOs = entries.stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
        
        return ResponseEntity.ok(entryDTOs);
    }
    
    @GetMapping("/statistics")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<BlockchainStatisticsDTO> getStatistics() {
        BlockchainStatistics stats = blockchainService.getBlockchainStatistics();
        BlockchainStatisticsDTO statsDTO = convertToDTO(stats);
        return ResponseEntity.ok(statsDTO);
    }
    
    @PostMapping("/validate")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<ValidationResultDTO> validateIntegrity() {
        boolean isValid = blockchainService.validateBlockchainIntegrity();
        
        ValidationResultDTO result = ValidationResultDTO.builder()
            .isValid(isValid)
            .timestamp(LocalDateTime.now())
            .message(isValid ? "Blockchain intègre" : "Blockchain compromise")
            .build();
        
        return ResponseEntity.ok(result);
    }
    
    @GetMapping("/audit/{auditId}/hash")
    public ResponseEntity<String> getAuditHash(@PathVariable Long auditId) {
        Audit audit = auditService.findById(auditId);
        String hash = blockchainService.getAuditHash(audit);
        return ResponseEntity.ok(hash);
    }
    
    @GetMapping("/visualization")
    public ResponseEntity<List<BlockchainVisualizationDTO>> getVisualization() {
        List<BlockchainEntry> recentEntries = blockchainService.getRecentEntries(50);
        
        List<BlockchainVisualizationDTO> visualization = recentEntries.stream()
            .map(this::convertToVisualizationDTO)
            .collect(Collectors.toList());
        
        return ResponseEntity.ok(visualization);
    }
}

3. Intercepteur Blockchain

// Fichier : config/BlockchainInterceptor.java
@Component
public class BlockchainInterceptor implements HandlerInterceptor {
    
    @Autowired
    private BlockchainService blockchainService;
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                              Object handler, Exception ex) throws Exception {
        
        // Enregistrer certaines actions automatiquement
        if (response.getStatus() == 200) {
            String actionType = determineActionType(request);
            if (actionType != null) {
                try {
                    // Récupérer l'utilisateur connecté
                    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                    if (auth != null && auth.getPrincipal() instanceof UserAccount) {
                        UserAccount user = (UserAccount) auth.getPrincipal();
                        
                        // Enregistrer l'action
                        blockchainService.recordAction(actionType, null, user);
                    }
                } catch (Exception e) {
                    log.error("Erreur enregistrement automatique blockchain: {}", e.getMessage());
                }
            }
        }
    }
    
    private String determineActionType(HttpServletRequest request) {
        String method = request.getMethod();
        String path = request.getRequestURI();
        
        if ("POST".equals(method)) {
            if (path.contains("/api/auth/login")) {
                return "USER_LOGIN";
            } else if (path.contains("/api/audits")) {
                return "AUDIT_CREATED";
            } else if (path.contains("/api/alerts")) {
                return "ALERT_CREATED";
            }
        } else if ("PUT".equals(method)) {
            if (path.contains("/api/audits") && path.contains("/complete")) {
                return "AUDIT_COMPLETED";
            } else if (path.contains("/api/alerts") && path.contains("/resolve")) {
                return "ALERT_RESOLVED";
            }
        }
        
        return null;
    }
}

4. DTOs

// Fichier : dto/BlockchainEntryDTO.java
public class BlockchainEntryDTO {
    private Long id;
    private String previousHash;
    private String currentHash;
    private String actionType;
    private String data;
    private String userIdentity;
    private LocalDateTime timestamp;
    private Long blockNumber;
    private String entityType;
    private Long entityId;
}

// Fichier : dto/BlockchainStatisticsDTO.java
public class BlockchainStatisticsDTO {
    private long totalBlocks;
    private long uniqueUsers;
    private long actionsToday;
    private Map<String, Long> actionsByType;
    private boolean isIntegrityValid;
    private LocalDateTime lastValidation;
}

// Fichier : dto/BlockchainVisualizationDTO.java
public class BlockchainVisualizationDTO {
    private Long blockNumber;
    private String actionType;
    private String userIdentity;
    private LocalDateTime timestamp;
    private String entityType;
    private String currentHash;
    private String previousHash;
}

🔧 OUTILS & TECHNOLOGIES

Spring Boot

  • Spring Data JPA : Persistance blockchain
  • @Transactional : Cohérence des données
  • Interceptors : Enregistrement automatique
  • Jackson : Sérialisation JSON

Cryptographie

  • SHA-256 : Hachage cryptographique
  • MessageDigest : Calcul de hash
  • Bouncy Castle : Fonctions crypto avancées

Dépendances Maven

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

🎯 CRITÈRES D'ACCEPTATION

Fonctionnalités Blockchain

  • 10 types d'actions : Toutes les actions importantes tracées
  • Hash SHA-256 : Calcul correct des hash
  • Chaînage : Chaque bloc lié au précédent
  • Validation : Vérification intégrité automatique
  • Performance : < 100ms enregistrement

API REST

  • Historique : Par entité et ID
  • Statistiques : Métriques blockchain
  • Validation : Vérification intégrité
  • Visualisation : Données pour interface

Sécurité

  • Immuabilité : Impossible de modifier
  • Traçabilité : Toutes les actions visibles
  • Intégrité : Détection modifications
  • Audit trail : Historique complet

📁 STRUCTURE DES FICHIERS

src/main/java/ci/dgi/smartaudit/
├── service/
│   ├── BlockchainService.java
│   └── BlockchainValidationService.java
├── controller/
│   └── BlockchainController.java
├── repository/
│   └── BlockchainEntryRepository.java
├── config/
│   └── BlockchainInterceptor.java
├── dto/
│   ├── BlockchainEntryDTO.java
│   ├── BlockchainStatisticsDTO.java
│   └── BlockchainVisualizationDTO.java
└── entity/
    └── BlockchainEntry.java

🧪 TESTS À IMPLÉMENTER

Tests Unitaires

@ExtendWith(MockitoExtension.class)
class BlockchainServiceTest {
    
    @Test
    void recordAction_WithValidData_ShouldCreateBlock() {
        // Test création bloc
    }
    
    @Test
    void validateBlockchainIntegrity_WithValidChain_ShouldReturnTrue() {
        // Test validation intégrité
    }
    
    @Test
    void calculateHash_WithSameData_ShouldReturnSameHash() {
        // Test calcul hash
    }
}

Tests d'Intégration

@SpringBootTest
@AutoConfigureTestDatabase
class BlockchainIntegrationTest {
    
    @Test
    void blockchainWorkflow_FromCreationToValidation_ShouldWork() {
        // Test workflow complet
    }
}

🚀 DÉMARRAGE RAPIDE

1. Configuration Blockchain

# application.yml
smartaudit:
  blockchain:
    validation-interval: 3600 # 1 heure
    max-blocks-per-request: 100
    enable-auto-logging: true

2. Initialisation Premier Bloc

@Component
public class BlockchainInitializer implements CommandLineRunner {
    
    @Autowired
    private BlockchainService blockchainService;
    
    @Override
    public void run(String... args) throws Exception {
        // Créer le bloc Genesis
        blockchainService.recordAction("SYSTEM_INIT", null, getSystemUser());
        log.info("Bloc Genesis créé");
    }
}

📊 MÉTRIQUES DE SUCCÈS

  • Temps enregistrement : < 100ms par bloc
  • Intégrité : 100% des blocs valides
  • Performance : < 500ms validation complète
  • Fiabilité : 0 perte de données

🔗 INTÉGRATIONS

Avec Issue #1

  • Authentification : Traçage connexions
  • Utilisateurs : Identité dans blocs

Avec Issue #3

  • Alertes : Création et résolution
  • IA : Détection automatique

Avec Issue #4

  • Audits : Workflow complet
  • Rapports : Génération tracée

Avec Issue #6

  • Rapports : Hash intégré
  • QR codes : Référence blockchain

⚠️ POINTS D'ATTENTION

  1. Performance : Optimiser requêtes blockchain
  2. Stockage : Croissance continue des données
  3. Sécurité : Protection clés privées
  4. Backup : Sauvegarde chaîne complète

ESTIMATION : 1.5 jours
DIFFICULTÉ : Moyenne
PRIORITÉ : Haute (transparence)