**Motivations:** - Resolve insufficient UTXO amount errors when wallet has many small UTXOs - Prevent race conditions when multiple anchor requests arrive simultaneously - Improve signet dashboard functionality and documentation **Root causes:** - API tried to find a single UTXO large enough instead of combining multiple UTXOs - No mutex mechanism to prevent concurrent transactions from using the same UTXOs - UTXOs in mempool still appear as available in listunspent before block confirmation **Correctifs:** - Implement coin selection algorithm to combine multiple UTXOs when needed - Add mutex-based locking mechanism to serialize UTXO access - Filter locked UTXOs during selection to prevent double spending - Properly handle change output when combining multiple UTXOs - Lock UTXOs during transaction creation and unlock after mempool broadcast **Evolutions:** - Enhance signet dashboard with improved Bitcoin RPC integration - Update mempool documentation - Add comprehensive fix documentation in fixKnowledge/ **Pages affectées:** - api-anchorage/src/bitcoin-rpc.js - signet-dashboard/src/bitcoin-rpc.js - signet-dashboard/src/server.js - signet-dashboard/public/app.js - signet-dashboard/public/index.html - signet-dashboard/public/styles.css - signet-dashboard/start.sh - docs/MEMPOOL.md - fixKnowledge/api-anchorage-insufficient-utxo.md (new) - fixKnowledge/api-anchorage-utxo-race-condition.md (new) - anchor_count.txt (new) - mempool (submodule update)
228 lines
7.5 KiB
Markdown
228 lines
7.5 KiB
Markdown
# Correction : Race condition sur les UTXOs dans l'API d'ancrage
|
|
|
|
**Auteur** : Équipe 4NK
|
|
**Date** : 2026-01-24
|
|
**Version** : 1.0
|
|
|
|
## Problème Identifié
|
|
|
|
Lorsque plusieurs requêtes d'ancrage arrivent simultanément, elles peuvent toutes voir les mêmes UTXOs comme disponibles et essayer de les utiliser, causant des erreurs car les UTXOs sont déjà dépensés dans le mempool avant d'être confirmés dans les blocs.
|
|
|
|
### Symptômes
|
|
|
|
- Plusieurs transactions simultanées tentent d'utiliser les mêmes UTXOs
|
|
- Erreurs "Transaction already in block chain" ou "Missing inputs"
|
|
- Les UTXOs semblent disponibles alors qu'ils sont déjà dans le mempool
|
|
- Problème de race condition lors de requêtes concurrentes
|
|
|
|
## Cause Racine
|
|
|
|
Les validations se font dans le mempool avant la confirmation dans les blocs. Pendant ce temps, les UTXOs utilisés dans le mempool apparaissent toujours comme disponibles dans `listunspent`, car ils ne sont pas encore confirmés dans un bloc.
|
|
|
|
**Problème technique** : Aucun mécanisme de verrouillage (mutex) n'était en place pour empêcher plusieurs transactions simultanées d'utiliser les mêmes UTXOs.
|
|
|
|
## Correctifs Appliqués
|
|
|
|
### 1. Implémentation d'un mutex pour l'accès aux UTXOs
|
|
|
|
**Fichier** : `api-anchorage/src/bitcoin-rpc.js`
|
|
|
|
**Ajout dans le constructeur** :
|
|
```javascript
|
|
// Mutex pour gérer l'accès concurrent aux UTXOs
|
|
// Utilise une Promise-based queue pour sérialiser les accès
|
|
this.utxoMutexPromise = Promise.resolve();
|
|
|
|
// Liste des UTXOs en cours d'utilisation (format: "txid:vout")
|
|
this.lockedUtxos = new Set();
|
|
```
|
|
|
|
**Méthode `acquireUtxoMutex()`** :
|
|
```javascript
|
|
async acquireUtxoMutex() {
|
|
// Attendre que le mutex précédent soit libéré
|
|
const previousMutex = this.utxoMutexPromise;
|
|
let releaseMutex;
|
|
|
|
// Créer une nouvelle Promise qui sera résolue quand le mutex est libéré
|
|
this.utxoMutexPromise = new Promise((resolve) => {
|
|
releaseMutex = resolve;
|
|
});
|
|
|
|
// Attendre que le mutex précédent soit libéré
|
|
await previousMutex;
|
|
|
|
// Retourner la fonction pour libérer le mutex
|
|
return releaseMutex;
|
|
}
|
|
```
|
|
|
|
**Impact** : Les requêtes sont sérialisées, une à la fois, pour l'accès aux UTXOs.
|
|
|
|
### 2. Liste des UTXOs verrouillés
|
|
|
|
**Méthodes ajoutées** :
|
|
- `isUtxoLocked(txid, vout)` : Vérifie si un UTXO est verrouillé
|
|
- `lockUtxo(txid, vout)` : Verrouille un UTXO
|
|
- `lockUtxos(utxos)` : Verrouille plusieurs UTXOs
|
|
- `unlockUtxo(txid, vout)` : Déverrouille un UTXO
|
|
- `unlockUtxos(utxos)` : Déverrouille plusieurs UTXOs
|
|
|
|
**Impact** : Les UTXOs en cours d'utilisation sont marqués comme verrouillés et ne peuvent pas être utilisés par d'autres transactions.
|
|
|
|
### 3. Filtrage des UTXOs verrouillés
|
|
|
|
**Modification dans `createAnchorTransaction()`** :
|
|
```javascript
|
|
// Filtrer les UTXOs verrouillés (en cours d'utilisation par d'autres transactions)
|
|
const availableUtxos = unspent.filter(utxo => !this.isUtxoLocked(utxo.txid, utxo.vout));
|
|
|
|
logger.info('Available UTXOs (after filtering locked)', {
|
|
total: unspent.length,
|
|
available: availableUtxos.length,
|
|
locked: unspent.length - availableUtxos.length,
|
|
// ...
|
|
});
|
|
```
|
|
|
|
**Impact** : Seuls les UTXOs non verrouillés sont considérés pour la sélection.
|
|
|
|
### 4. Verrouillage et déverrouillage des UTXOs
|
|
|
|
**Dans `createAnchorTransaction()`** :
|
|
```javascript
|
|
// Acquérir le mutex pour l'accès aux UTXOs
|
|
const releaseMutex = await this.acquireUtxoMutex();
|
|
let selectedUtxos = [];
|
|
|
|
try {
|
|
// ... sélection des UTXOs ...
|
|
|
|
// Verrouiller les UTXOs sélectionnés
|
|
this.lockUtxos(selectedUtxos);
|
|
|
|
// ... création et envoi de la transaction ...
|
|
|
|
// Déverrouiller les UTXOs après l'envoi au mempool
|
|
this.unlockUtxos(selectedUtxos);
|
|
releaseMutex();
|
|
|
|
} catch (error) {
|
|
// En cas d'erreur, déverrouiller les UTXOs et libérer le mutex
|
|
if (selectedUtxos.length > 0) {
|
|
this.unlockUtxos(selectedUtxos);
|
|
}
|
|
releaseMutex();
|
|
throw error;
|
|
}
|
|
```
|
|
|
|
**Impact** : Les UTXOs sont verrouillés pendant la création de la transaction et déverrouillés après l'envoi au mempool ou en cas d'erreur.
|
|
|
|
## Modifications
|
|
|
|
### Fichiers Modifiés
|
|
|
|
- `api-anchorage/src/bitcoin-rpc.js` :
|
|
- Ajout du mutex et de la liste des UTXOs verrouillés
|
|
- Filtrage des UTXOs verrouillés lors de la sélection
|
|
- Verrouillage/déverrouillage des UTXOs sélectionnés
|
|
|
|
### Fichiers Créés
|
|
|
|
- `fixKnowledge/api-anchorage-utxo-race-condition.md` : Cette documentation
|
|
|
|
## Modalités de Déploiement
|
|
|
|
### Redémarrage de l'API
|
|
|
|
1. **Arrêter l'API** :
|
|
```bash
|
|
ps aux | grep "node.*api-anchorage" | grep -v grep | awk '{print $2}' | xargs kill
|
|
```
|
|
|
|
2. **Redémarrer l'API** :
|
|
```bash
|
|
cd /home/ncantu/Bureau/code/bitcoin/api-anchorage
|
|
npm start
|
|
```
|
|
|
|
### Vérification
|
|
|
|
1. **Tester avec des requêtes simultanées** :
|
|
```bash
|
|
# Lancer plusieurs requêtes en parallèle
|
|
for i in {1..5}; do
|
|
curl -X POST http://localhost:3010/api/anchor/document \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'x-api-key: 770b9b33-8a15-4a6d-8f95-1cd2b36e7376' \
|
|
--data-raw "{\"hash\":\"$(openssl rand -hex 32)\"}" &
|
|
done
|
|
wait
|
|
```
|
|
|
|
2. **Vérifier les logs** :
|
|
```bash
|
|
tail -f /tmp/anchorage-api.log | grep -E "(UTXO|locked|mutex|Selected)"
|
|
```
|
|
|
|
## Modalités d'Analyse
|
|
|
|
### Vérification que la correction fonctionne
|
|
|
|
1. **Vérifier que les requêtes sont sérialisées** :
|
|
- Les logs doivent montrer que les requêtes sont traitées une à la fois
|
|
- Les UTXOs verrouillés ne doivent pas être sélectionnés
|
|
|
|
2. **Vérifier qu'il n'y a pas d'erreurs de double dépense** :
|
|
- Aucune erreur "Transaction already in block chain"
|
|
- Aucune erreur "Missing inputs"
|
|
- Toutes les transactions doivent être créées avec succès
|
|
|
|
3. **Vérifier les logs de verrouillage** :
|
|
- Les logs doivent afficher "Available UTXOs (after filtering locked)"
|
|
- Les logs doivent montrer le nombre d'UTXOs verrouillés
|
|
|
|
### Cas limites
|
|
|
|
1. **Tous les UTXOs sont verrouillés** :
|
|
- L'erreur doit indiquer "No available UTXOs (all are locked or in use)"
|
|
- Les requêtes doivent attendre que des UTXOs soient déverrouillés
|
|
|
|
2. **Requêtes simultanées** :
|
|
- Les requêtes doivent être traitées séquentiellement
|
|
- Chaque requête doit utiliser des UTXOs différents
|
|
|
|
3. **Erreur lors de la création de la transaction** :
|
|
- Les UTXOs doivent être déverrouillés même en cas d'erreur
|
|
- Le mutex doit être libéré même en cas d'erreur
|
|
|
|
## Résultat
|
|
|
|
✅ **Problème résolu**
|
|
|
|
- Les requêtes concurrentes sont sérialisées via un mutex
|
|
- Les UTXOs en cours d'utilisation sont verrouillés
|
|
- Les UTXOs verrouillés sont filtrés lors de la sélection
|
|
- Les UTXOs sont déverrouillés après l'envoi au mempool ou en cas d'erreur
|
|
- Plus de race condition sur les UTXOs
|
|
|
|
**Exemple de transaction réussie avec mutex** :
|
|
- Transaction ID : `ddcf585d703966ca206972b4ee662aa00ee820de9dc1a8a33789ac02cf578dfd`
|
|
- Les UTXOs sont correctement verrouillés et déverrouillés
|
|
|
|
## Prévention
|
|
|
|
Pour éviter ce problème à l'avenir :
|
|
|
|
1. **Toujours utiliser un mutex** pour les opérations critiques qui partagent des ressources
|
|
2. **Verrouiller les ressources** avant de les utiliser
|
|
3. **Déverrouiller les ressources** après utilisation ou en cas d'erreur
|
|
4. **Filtrer les ressources verrouillées** lors de la sélection
|
|
5. **Tester avec des requêtes simultanées** pour détecter les race conditions
|
|
|
|
## Pages Affectées
|
|
|
|
- `api-anchorage/src/bitcoin-rpc.js` : Implémentation du mutex et de la gestion des UTXOs verrouillés
|
|
- `fixKnowledge/api-anchorage-utxo-race-condition.md` : Documentation (nouveau)
|