**Motivations:** - Synchronisation des modifications sur l'API anchorage, les services et le website skeleton - Ajout de scripts de monitoring et de diagnostic pour l'API anchorage - Documentation des problèmes de mutex et de provisioning UTXO **Root causes:** - N/A (commit de synchronisation) **Correctifs:** - N/A (commit de synchronisation) **Evolutions:** - Ajout de scripts de monitoring et de diagnostic pour l'API anchorage - Amélioration de la gestion des mutex et des UTXOs - Mise à jour de la documentation **Pages affectées:** - api-anchorage/src/bitcoin-rpc.js - api-anchorage/src/routes/anchor.js - api-anchorage/src/routes/health.js - api-anchorage/src/server.js - api-anchorage/README-MONITORING.md - api-anchorage/cleanup-stale-locks.mjs - api-anchorage/diagnose.mjs - api-anchorage/unlock-utxos.mjs - service-login-verify/src/persistentNonceCache.ts - signet-dashboard/src/server.js - signet-dashboard/public/* - userwallet/src/hooks/useChannel.ts - userwallet/src/services/relayNotificationService.ts - userwallet/src/utils/defaultContract.ts - website-skeleton/src/* - docs/DOMAINS_AND_PORTS.md - docs/INTERFACES.md - features/* - fixKnowledge/*
477 lines
14 KiB
Markdown
477 lines
14 KiB
Markdown
# 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)
|