# Analyse: Causes possibles du blocage du mutex et UTXOs verrouillés **Date:** 2026-01-28 **Auteur:** Équipe 4NK ## Vue d'ensemble Ce document analyse les causes possibles du blocage du mutex et des UTXOs verrouillés dans l'API d'ancrage, ainsi que les moyens de contrôle et de correction. ## Causes possibles identifiées ### 1. Timeout RPC Bitcoin (ESOCKETTIMEDOUT) **Cause:** - Les appels RPC vers Bitcoin Core timeout (défaut: 30s, configuré: 60s) - Si un appel RPC bloque indéfiniment, le code ne peut pas continuer - Le mutex reste acquis et les UTXOs restent verrouillés **Scénarios:** - Bitcoin Core surchargé ou non réactif - Problème réseau entre l'API et Bitcoin Core - Bitcoin Core en cours de synchronisation ou de traitement lourd - Wallet Bitcoin verrouillé ou non accessible **Code concerné:** - `bitcoin-rpc.js` ligne 23: `timeout: parseInt(process.env.BITCOIN_RPC_TIMEOUT || '30000')` - `.env` ligne 6: `BITCOIN_RPC_TIMEOUT=60000` **Indicateurs:** - Logs: `Error getting balance: ESOCKETTIMEDOUT` - Logs: `Failed to get balance: ESOCKETTIMEDOUT` - Mutex bloqué pendant plus de 60 secondes ### 2. Promise.all() qui ne se résout jamais **Cause:** - Ligne 253: `await Promise.all(addressPromises)` peut bloquer si une Promise ne se résout jamais - Si `getNewAddress()` timeout ou échoue silencieusement, la Promise reste en attente - Le mutex reste acquis **Scénarios:** - Bitcoin RPC ne répond pas à `getNewAddress()` - Timeout RPC trop court pour certaines opérations - Erreur réseau non gérée **Code concerné:** - `bitcoin-rpc.js` lignes 248-253: Génération des adresses en parallèle **Indicateurs:** - Logs montrent "Anchor request received" mais pas de suite - Pas d'erreur dans les logs, mais la requête ne se termine jamais ### 3. Erreur non gérée avant le bloc finally **Cause:** - Si une erreur se produit avant d'atteindre le bloc `finally` (ligne 908), le mutex peut ne pas être libéré - Exemple: erreur fatale Node.js, OOM (Out of Memory), crash du processus **Scénarios:** - Erreur fatale Node.js (segfault, assertion failed) - Manque de mémoire (OOM killer) - Arrêt brutal du processus (kill -9) - Exception non catchée dans une fonction asynchrone **Code concerné:** - `bitcoin-rpc.js` ligne 908-914: Bloc `finally` qui libère le mutex **Indicateurs:** - Processus Node.js redémarré par systemd - Logs montrent un arrêt brutal sans message d'erreur - UTXOs verrouillés après redémarrage ### 4. Erreur dans le bloc finally lui-même **Cause:** - Si `releaseMutex()` échoue dans le bloc `finally`, le mutex reste bloqué - Actuellement, l'erreur est seulement loggée en WARN, mais le mutex n'est pas libéré **Scénarios:** - Exception lors de l'appel à `releaseMutex()` - Problème avec la Promise du mutex **Code concerné:** - `bitcoin-rpc.js` lignes 910-914: Gestion d'erreur dans le `finally` **Indicateurs:** - Logs: `Error releasing mutex` - Mutex reste bloqué malgré le `finally` ### 5. Déverrouillage des UTXOs qui échoue silencieusement **Cause:** - Ligne 903: `this.unlockUtxo()` peut échouer silencieusement (ligne 152-158) - Si la mise à jour de la base de données échoue, l'UTXO reste verrouillé - Le mutex est libéré, mais les UTXOs restent verrouillés en base **Scénarios:** - Base de données verrouillée (WAL mode, autre transaction en cours) - Erreur SQL (contrainte, table verrouillée) - Problème de permissions sur la base de données **Code concerné:** - `bitcoin-rpc.js` lignes 142-159: `unlockUtxo()` avec gestion d'erreur silencieuse - `bitcoin-rpc.js` lignes 899-905: Déverrouillage en cas d'erreur **Indicateurs:** - Mutex libéré (pas de timeout) - UTXOs toujours verrouillés dans la base de données - Logs: `Error updating UTXO unlock status in database` ### 6. Transaction SQL qui échoue partiellement **Cause:** - Si plusieurs UTXOs sont verrouillés mais que le déverrouillage échoue pour certains - La boucle continue mais certains UTXOs restent verrouillés **Scénarios:** - Erreur SQL pour un UTXO spécifique - UTXO supprimé de la base pendant le déverrouillage - Contrainte de base de données violée **Code concerné:** - `bitcoin-rpc.js` lignes 900-904: Boucle de déverrouillage **Indicateurs:** - Certains UTXOs déverrouillés, d'autres non - Logs montrant des erreurs pour certains UTXOs ### 7. Race condition lors de l'acquisition du mutex **Cause:** - Si plusieurs requêtes arrivent simultanément, elles peuvent toutes acquérir le mutex - Le timeout de 180s peut être atteint avant que toutes les requêtes ne se terminent **Scénarios:** - Pic de charge avec de nombreuses requêtes simultanées - Requêtes qui prennent plus de 180s chacune - Accumulation de requêtes en attente **Code concerné:** - `bitcoin-rpc.js` lignes 42-76: `acquireUtxoMutex()` avec timeout de 180s **Indicateurs:** - Nombreux timeouts de mutex dans les logs - Requêtes qui attendent plus de 180s ### 8. Problème de connexion à la base de données **Cause:** - Si la connexion à la base de données est perdue, les opérations de verrouillage/déverrouillage échouent - Le mutex peut être libéré en mémoire, mais les UTXOs restent verrouillés en base **Scénarios:** - Base de données corrompue - Connexion SQLite fermée - Problème de permissions **Code concerné:** - `database.js`: Gestion de la connexion SQLite - Toutes les opérations de verrouillage/déverrouillage **Indicateurs:** - Erreurs SQL dans les logs - Base de données inaccessible ## Moyens de contrôle ### 1. Monitoring des UTXOs verrouillés **Script de vérification:** ```bash # Vérifier le nombre d'UTXOs verrouillés curl http://localhost:3010/api/anchor/locked-utxos | jq '.count' # Si > 0, il y a un problème ``` **Alertes recommandées:** - Warning si > 5 UTXOs verrouillés - Critical si > 10 UTXOs verrouillés - Critical si UTXOs verrouillés depuis > 10 minutes ### 2. Monitoring des timeouts de mutex **Vérification dans les logs:** ```bash # Compter les timeouts de mutex dans les dernières heures sudo journalctl -u anchorage-api --since "1 hour ago" | grep -c "Mutex acquisition timeout" # Si > 0, il y a un problème ``` **Alertes recommandées:** - Warning si > 1 timeout par heure - Critical si > 5 timeouts par heure ### 3. Monitoring des erreurs RPC Bitcoin **Vérification dans les logs:** ```bash # Compter les erreurs RPC Bitcoin sudo journalctl -u anchorage-api --since "1 hour ago" | grep -c "ESOCKETTIMEDOUT\|ETIMEDOUT\|ECONNRESET" # Si > 0, problème de connexion Bitcoin RPC ``` **Alertes recommandées:** - Warning si > 1 erreur RPC par heure - Critical si > 5 erreurs RPC par heure ### 4. Monitoring de la durée des opérations **Vérification:** - Logger le temps d'exécution de `createAnchorTransaction()` - Alerter si > 30 secondes (normal: < 10 secondes) **Code à ajouter:** ```javascript const startTime = Date.now(); try { // ... opération ... } finally { const duration = Date.now() - startTime; if (duration > 30000) { logger.warn('Anchor transaction took too long', { duration, hash }); } } ``` ### 5. Health check amélioré **Endpoint à créer:** ```javascript GET /health/detailed { "ok": true, "mutex": { "locked": false, "waiting": 0 }, "utxos": { "locked": 0, "locked_since": null }, "bitcoin": { "connected": true, "rpc_timeout": 60000 } } ``` ### 6. Script de maintenance automatique **Script cron à créer:** ```bash #!/bin/bash # Déverrouiller les UTXOs verrouillés depuis plus de 10 minutes LOCKED_COUNT=$(curl -s http://localhost:3010/api/anchor/locked-utxos | jq '.count') if [ "$LOCKED_COUNT" -gt 0 ]; then # Vérifier l'âge des verrouillages # Si > 10 minutes, déverrouiller automatiquement cd /home/ncantu/Bureau/code/bitcoin/api-anchorage node unlock-utxos.mjs fi ``` ## Moyens de correction ### 1. Correction immédiate (déjà implémentée) **Script de déverrouillage:** - `api-anchorage/unlock-utxos.mjs`: Déverrouille tous les UTXOs verrouillés **Utilisation:** ```bash cd /home/ncantu/Bureau/code/bitcoin/api-anchorage node unlock-utxos.mjs ``` ### 2. Améliorations préventives à implémenter #### A. Timeout de sécurité sur le mutex **Problème:** Le mutex peut rester acquis indéfiniment si l'opération se bloque. **Solution:** Ajouter un timeout de sécurité qui libère automatiquement le mutex après un délai maximum. **Code à ajouter:** ```javascript async createAnchorTransaction(...) { const releaseMutex = await this.acquireUtxoMutex(); let mutexSafetyTimeout; // Timeout de sécurité: libérer le mutex après 5 minutes maximum mutexSafetyTimeout = setTimeout(() => { logger.error('Mutex held for too long, forcing release', { hash }); releaseMutex(); }, 300000); // 5 minutes try { // ... opération ... } finally { clearTimeout(mutexSafetyTimeout); releaseMutex(); } } ``` #### B. Timeout sur Promise.all() **Problème:** `Promise.all()` peut bloquer indéfiniment si une Promise ne se résout jamais. **Solution:** Ajouter un timeout sur `Promise.all()`. **Code à modifier:** ```javascript // Ligne 253: Remplacer const allAddresses = await Promise.all(addressPromises); // Par: const allAddresses = await Promise.race([ Promise.all(addressPromises), new Promise((_, reject) => setTimeout(() => reject(new Error('Address generation timeout')), 30000) ) ]); ``` #### C. Déverrouillage automatique des UTXOs anciens **Problème:** Les UTXOs peuvent rester verrouillés si le processus crash. **Solution:** Déverrouiller automatiquement les UTXOs verrouillés depuis plus de 10 minutes au démarrage. **Code à ajouter dans `server.js`:** ```javascript // Au démarrage du serveur const db = getDatabase(); const result = db.prepare(` UPDATE utxos SET is_locked_in_mutex = 0 WHERE is_locked_in_mutex = 1 AND updated_at < datetime('now', '-10 minutes') `).run(); if (result.changes > 0) { logger.info('Unlocked stale UTXOs on startup', { count: result.changes }); } ``` #### D. Retry avec backoff pour les appels RPC **Problème:** Les timeouts RPC peuvent être temporaires. **Solution:** Implémenter un retry avec backoff exponentiel. **Code à ajouter:** ```javascript async callRPCWithRetry(method, params, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await this.client[method](...params); } catch (error) { if (i === maxRetries - 1) throw error; const delay = Math.min(1000 * Math.pow(2, i), 10000); logger.warn(`RPC call failed, retrying in ${delay}ms`, { method, attempt: i + 1 }); await new Promise(resolve => setTimeout(resolve, delay)); } } } ``` #### E. Monitoring et alertes **Problème:** Les problèmes ne sont détectés qu'après coup. **Solution:** Implémenter un système de monitoring proactif. **À implémenter:** - Endpoint `/health/detailed` avec état du mutex et UTXOs - Script cron pour vérifier et déverrouiller automatiquement - Alertes (email, webhook) en cas de problème ### 3. Scripts de diagnostic #### A. Script de diagnostic complet **Créer:** `api-anchorage/diagnose.mjs` ```javascript #!/usr/bin/env node import { getDatabase } from './src/database.js'; const db = getDatabase(); // UTXOs verrouillés const locked = db.prepare(` SELECT txid, vout, address, amount, updated_at, (julianday('now') - julianday(updated_at)) * 24 * 60 as minutes_locked FROM utxos WHERE is_locked_in_mutex = 1 ORDER BY updated_at `).all(); console.log(`\n📊 UTXOs verrouillés: ${locked.length}`); if (locked.length > 0) { console.table(locked.map(u => ({ txid: u.txid.substring(0, 16) + '...', vout: u.vout, amount: u.amount, locked_for_minutes: Math.round(u.minutes_locked * 100) / 100 }))); } // UTXOs verrouillés depuis plus de 10 minutes const stale = locked.filter(u => u.minutes_locked > 10); if (stale.length > 0) { console.log(`\n⚠️ UTXOs verrouillés depuis plus de 10 minutes: ${stale.length}`); } db.close(); ``` #### B. Script de nettoyage automatique **Créer:** `api-anchorage/cleanup-stale-locks.mjs` ```javascript #!/usr/bin/env node import { getDatabase } from './src/database.js'; const db = getDatabase(); const result = db.prepare(` UPDATE utxos SET is_locked_in_mutex = 0 WHERE is_locked_in_mutex = 1 AND updated_at < datetime('now', '-10 minutes') `).run(); console.log(`✅ UTXOs déverrouillés: ${result.changes}`); db.close(); ``` **Cron job:** ```bash # Toutes les 5 minutes */5 * * * * cd /home/ncantu/Bureau/code/bitcoin/api-anchorage && node cleanup-stale-locks.mjs ``` ## Recommandations prioritaires ### Priorité 1 (Critique - à implémenter immédiatement) 1. **Déverrouillage automatique au démarrage** (solution C) 2. **Timeout de sécurité sur le mutex** (solution A) 3. **Script cron de nettoyage automatique** (solution B) ### Priorité 2 (Important - à implémenter rapidement) 4. **Health check amélioré** (solution 5) 5. **Monitoring des UTXOs verrouillés** (solution 1) 6. **Timeout sur Promise.all()** (solution B) ### Priorité 3 (Amélioration - à planifier) 7. **Retry avec backoff pour RPC** (solution D) 8. **Monitoring et alertes** (solution E) 9. **Scripts de diagnostic** (solution 3) ## Pages affectées - `api-anchorage/src/bitcoin-rpc.js`: Améliorations du mutex et gestion d'erreurs - `api-anchorage/src/server.js`: Déverrouillage au démarrage - `api-anchorage/unlock-utxos.mjs`: Script de déverrouillage (existant) - `api-anchorage/cleanup-stale-locks.mjs`: Script de nettoyage automatique (à créer) - `api-anchorage/diagnose.mjs`: Script de diagnostic (à créer) - `fixKnowledge/api-anchorage-mutex-blockage-analysis.md`: Documentation (nouveau)