feat: Add APIs, dashboard, documentation and improve scripts

**Motivations:**
- Add API services for anchorage and faucet functionality
- Add dashboard interface for signet monitoring
- Improve documentation and maintenance guides
- Enhance existing scripts for better functionality

**Root causes:**
- Need for API services to interact with Bitcoin Signet
- Need for user-friendly dashboard interface
- Need for comprehensive documentation
- Scripts required improvements for better reliability

**Correctifs:**
- Updated Dockerfile with better configuration
- Improved gen-bitcoind-conf.sh and gen-signet-keys.sh scripts
- Enhanced mine.sh, miner, run.sh, and setup-signet.sh scripts
- Updated env.example with new configuration options

**Evolutions:**
- Added api-anchorage service with anchor functionality
- Added api-faucet service for testnet coin distribution
- Added signet-dashboard for monitoring and management
- Added comprehensive documentation in docs/ directory
- Added configure-nginx-proxy.sh for proxy configuration
- Added update-signet.sh for signet updates
- Added ETAT_SYSTEME.md and START_DASHBOARD_AND_FAUCET.md guides
- Added .bitcoin-version file for version tracking

**Pages affectées:**
- Dockerfile
- env.example
- gen-bitcoind-conf.sh
- gen-signet-keys.sh
- mine.sh
- miner
- run.sh
- setup-signet.sh
- api-anchorage/ (new)
- api-faucet/ (new)
- signet-dashboard/ (new)
- docs/ (new)
- configure-nginx-proxy.sh (new)
- update-signet.sh (new)
- ETAT_SYSTEME.md (new)
- START_DASHBOARD_AND_FAUCET.md (new)
- .bitcoin-version (new)
- .env (modified)
- mempool/ (added)
This commit is contained in:
ncantu 2026-01-24 02:40:25 +01:00
parent 9f304317d6
commit 20d115a31c
59 changed files with 12055 additions and 45 deletions

1
.bitcoin-version Normal file
View File

@ -0,0 +1 @@
30.2

15
.env
View File

@ -1,16 +1,17 @@
# Mining Configuration
BLOCKPRODUCTIONDELAY=60
# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported)
BLOCKPRODUCTIONDELAY=600
MINERENABLED=1
NBITS=1e0377ae
PRIVKEY=cQ78Y7oUASnzbgK465VPug4CrfxJPe95ftsDqwMwTJU8j1NoY76C
MINETO=tb1qhcuywsgshgs2ukv0ae2gw9egrfqx944qh6eurq
SIGNETCHALLENGE=5121021dc5794f479c961e47fd3ec343ecd7bb43c3abb991fb1e104f1e2403019e5e7551ae
PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS
MINETO=
SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
# RPC Configuration
RPCUSER=easepay-signet
RPCPASSWORD=Beautifulgirl456
RPCUSER=bitcoin
RPCPASSWORD=bitcoin
UACOMMENT=Easepay
UACOMMENT=CustomSignet
# ZMQ Configuration
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332

View File

@ -1,6 +1,6 @@
FROM debian:buster-slim as builder
FROM debian:bookworm-slim as builder
ARG BITCOIN_VERSION=${BITCOIN_VERSION:-25.1}
ARG BITCOIN_VERSION=${BITCOIN_VERSION:-30.2}
ARG TARGETPLATFORM
@ -9,9 +9,11 @@ RUN apt-get update && \
WORKDIR /tmp
# Install Bitcoin binaries based on platform
RUN case $TARGETPLATFORM in \
linux/amd64) export TRIPLET="x86_64-linux-gnu";; \
linux/arm64) export TRIPLET="aarch64-linux-gnu";; \
RUN ARCH=$(uname -m) && \
case $ARCH in \
x86_64) export TRIPLET="x86_64-linux-gnu";; \
aarch64) export TRIPLET="aarch64-linux-gnu";; \
*) echo "Unsupported architecture: $ARCH" && exit 1;; \
esac && \
BITCOIN_URL="https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \
BITCOIN_FILE="bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \
@ -19,7 +21,7 @@ RUN case $TARGETPLATFORM in \
mkdir -p bin && \
tar -xzvf "${BITCOIN_FILE}" -C /tmp/bin --strip-components=2 "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli" "bitcoin-${BITCOIN_VERSION}/bin/bitcoind" "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-wallet" "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-util"
FROM debian:buster-slim as custom-signet-bitcoin
FROM debian:bookworm-slim as custom-signet-bitcoin
ENV BITCOIN_DIR /root/.bitcoin
@ -50,7 +52,7 @@ ENV EXTERNAL_IP=${EXTERNAL_IP:-""}
VOLUME $BITCOIN_DIR
EXPOSE 28332 28333 28334 38332 38333 38334
RUN apt-get update && \
apt-get install -qq --no-install-recommends procps python3 python3-pip jq && \
apt-get install -qq --no-install-recommends procps python3 python3-pip python3-setuptools jq && \
apt-get clean
COPY --from=builder "/tmp/bin" /usr/local/bin
COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh
@ -69,9 +71,7 @@ COPY *.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/*.sh
COPY rpcauth.py /usr/local/bin/rpcauth.py
RUN chmod +x /usr/local/bin/rpcauth.py
RUN pip3 install setuptools
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["run.sh"]

87
ETAT_SYSTEME.md Normal file
View File

@ -0,0 +1,87 @@
# État du Système Bitcoin Signet
**Date** : 2026-01-23
## ✅ État de la Chaîne
- **Chaîne** : Signet
- **Hauteur** : 0 (bloc genesis)
- **Hash du meilleur bloc** : `00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6`
- **Statut** : ✅ **UP** - Chaîne opérationnelle
## ✅ État du Miner
- **bitcoind** : ✅ **UP** - Processus actif (PID visible)
- **mine.sh** : ✅ **UP** - Script de mining actif
- **Statut** : ✅ **UP** - Miner opérationnel
## 💰 Balance du Miner
- **Balance mature** : 0.00000000 BTC
- **Balance immature** : 0.00000000 BTC
- **Total** : 0.00000000 BTC
**Note** : La balance est à 0 car aucun bloc n'a encore été miné depuis le bloc genesis. Le prochain bloc miné apportera la récompense de minage.
## ⚠️ API d'Ancrage
- **Port** : 3010
- **Statut** : ❌ **DOWN** - API non accessible
- **Action requise** : Lancer l'API d'ancrage
## ⚠️ Dashboard et API Faucet
- **Node.js** : ❌ **NON INSTALLÉ** sur cette machine
- **Dashboard (port 3020)** : ❌ **NON LANCÉ** - Nécessite Node.js
- **API Faucet (port 3021)** : ❌ **NON LANCÉ** - Nécessite Node.js
## Actions Requises
### Pour lancer le Dashboard et l'API Faucet
1. **Installer Node.js** (version >= 18.0.0) :
```bash
# Sur Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
```
2. **Installer les dépendances** :
```bash
cd signet-dashboard && npm install
cd ../api-faucet && npm install
```
3. **Configurer les fichiers .env** :
```bash
cd signet-dashboard && cp .env.example .env
cd ../api-faucet && cp .env.example .env
```
4. **Lancer les services** :
```bash
# Dashboard
cd signet-dashboard && npm start &
# API Faucet
cd api-faucet && npm start &
```
### Pour lancer l'API d'Ancrage
Vérifier si l'API d'ancrage est configurée et la lancer si nécessaire.
## Vérification
Une fois les services lancés, vérifier avec :
```bash
# Dashboard
curl http://localhost:3020/api/blockchain/info
# API Faucet
curl http://localhost:3021/health
# API Anchor
curl http://localhost:3010/health
```

View File

@ -0,0 +1,101 @@
# Guide de Démarrage Rapide - Dashboard et API Faucet
## Vue d'ensemble
Deux nouveaux services ont été créés pour Bitcoin Signet :
1. **Dashboard de Supervision** (port 3020)
2. **API Faucet** (port 3021)
## Installation Rapide
### 1. Dashboard de Supervision
```bash
cd signet-dashboard
npm install
cp .env.example .env
# Éditer .env avec vos paramètres
npm start
```
### 2. API Faucet
```bash
cd api-faucet
npm install
cp .env.example .env
# Éditer .env avec vos paramètres
npm start
```
## Configuration Minimale
### Dashboard (.env)
```bash
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
DASHBOARD_PORT=3020
ANCHOR_API_URL=http://localhost:3010
ANCHOR_API_KEY=your-api-key-here
FAUCET_API_URL=http://localhost:3021
```
### API Faucet (.env)
```bash
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
FAUCET_API_PORT=3021
FAUCET_AMOUNT=0.0005
```
## Démarrage avec PM2 (Production)
### Dashboard
```bash
cd signet-dashboard
pm2 start src/server.js --name signet-dashboard
pm2 save
```
### API Faucet
```bash
cd api-faucet
pm2 start src/server.js --name faucet-api
pm2 save
```
## Accès
- **Dashboard** : http://localhost:3020
- **API Faucet Health** : http://localhost:3021/health
## Vérification
```bash
# Vérifier que les services sont actifs
pm2 status
# Vérifier les logs
pm2 logs signet-dashboard
pm2 logs faucet-api
# Tester le dashboard
curl http://localhost:3020/api/blockchain/info
# Tester l'API faucet
curl http://localhost:3021/health
```
## Documentation Complète
- Dashboard : `signet-dashboard/README.md`
- API Faucet : `api-faucet/README.md`

Binary file not shown.

View File

@ -0,0 +1,20 @@
# Bitcoin RPC Configuration
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
BITCOIN_RPC_TIMEOUT=30000
# API Configuration
API_PORT=3010
API_HOST=0.0.0.0
# API Keys (séparées par des virgules)
API_KEYS=your-api-key-here,another-api-key
# Logging
LOG_LEVEL=info
# Mining Configuration
MINING_ENABLED=true
MINING_FEE_RATE=0.00001

7
api-anchorage/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
node_modules/
.env
*.log
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*

399
api-anchorage/DEPLOYMENT.md Normal file
View File

@ -0,0 +1,399 @@
# Guide de Déploiement - API d'Ancrage Bitcoin Signet
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0
## Vue d'Ensemble
Ce guide explique comment déployer l'API d'ancrage Bitcoin Signet sur le serveur de production (192.168.1.103) et la configurer pour être accessible via nginx sur le proxy (192.168.1.100) au domaine `certificator.4nkweb.com`.
## Architecture
```
Internet
↓ HTTPS
certificator.4nkweb.com (DNS → 4nk.myftp.biz)
Nginx Proxy (192.168.1.100:443)
↓ HTTP
API Anchor (192.168.1.103:3010)
↓ RPC
Bitcoin Signet Node (192.168.1.103:38332)
```
## Prérequis
- Serveur de production (192.168.1.103) accessible
- Nginx configuré sur le proxy (192.168.1.100)
- Nœud Bitcoin Signet fonctionnel sur 192.168.1.103:38332
- Node.js >= 18.0.0 installé sur le serveur de production
- Accès SSH au serveur de production
## Déploiement sur le Serveur de Production
### 1. Préparation du Serveur
```bash
# Se connecter au serveur de production
ssh prod
# Vérifier Node.js
node --version # Doit être >= 18.0.0
# Installer Node.js si nécessaire
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
```
### 2. Cloner et Installer l'API
```bash
# Créer le répertoire de déploiement
sudo mkdir -p /srv/4NK/certificator.4nkweb.com
sudo chown ncantu:ncantu /srv/4NK/certificator.4nkweb.com
cd /srv/4NK/certificator.4nkweb.com
# Cloner le dépôt (ou copier les fichiers)
# Si le code est dans un dépôt git :
# git clone <repository-url> .
# Ou copier les fichiers depuis votre machine locale
# scp -r api-anchorage/* ncantu@192.168.1.103:/srv/4NK/certificator.4nkweb.com/
```
### 3. Installation des Dépendances
```bash
cd /srv/4NK/certificator.4nkweb.com
npm install --production
```
### 4. Configuration
```bash
# Créer le fichier .env
cp .env.example .env
nano .env
```
Configuration recommandée pour la production :
```bash
# Bitcoin RPC Configuration
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
BITCOIN_RPC_TIMEOUT=30000
# API Configuration
API_PORT=3010
API_HOST=0.0.0.0
# API Keys (séparées par des virgules)
# IMPORTANT: Générer des clés sécurisées pour la production
API_KEYS=prod-api-key-1,prod-api-key-2
# Logging
LOG_LEVEL=info
# Mining Configuration
MINING_ENABLED=true
MINING_FEE_RATE=0.00001
```
**Sécurité** :
- Générer des clés API fortes (ex: `openssl rand -hex 32`)
- Ne jamais commiter le fichier `.env`
- Utiliser des mots de passe RPC différents de ceux par défaut en production
### 5. Test Local
```bash
# Tester que l'API démarre
npm start
# Dans un autre terminal, tester
curl http://localhost:3010/health
```
### 6. Déploiement avec PM2 (Recommandé)
```bash
# Installer PM2 globalement
sudo npm install -g pm2
# Démarrer l'API avec PM2
pm2 start src/server.js --name anchor-api
# Sauvegarder la configuration PM2
pm2 save
# Configurer PM2 pour démarrer au boot
pm2 startup
# Suivre les instructions affichées
```
### 7. Vérification
```bash
# Vérifier que l'API fonctionne
curl http://localhost:3010/health
# Vérifier les logs PM2
pm2 logs anchor-api
# Vérifier le statut
pm2 status
```
## Configuration Nginx sur le Proxy
### 1. Se Connecter au Proxy
```bash
ssh proxy
```
### 2. Créer la Configuration Nginx
```bash
sudo nano /etc/nginx/sites-available/certificator.4nkweb.com
```
Contenu :
```nginx
# API Anchor sur certificator.4nkweb.com
server {
listen 443 ssl http2;
server_name certificator.4nkweb.com;
# Certificats SSL (Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/certificator.4nkweb.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/certificator.4nkweb.com/privkey.pem;
# Configuration SSL
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Logs
access_log /var/log/nginx/certificator.4nkweb.com.access.log;
error_log /var/log/nginx/certificator.4nkweb.com.error.log;
# Proxy vers l'API
location / {
proxy_pass http://192.168.1.103:3010;
proxy_http_version 1.1;
# Headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Cache
proxy_cache_bypass $http_upgrade;
}
}
# Redirection HTTP vers HTTPS
server {
listen 80;
server_name certificator.4nkweb.com;
return 301 https://$server_name$request_uri;
}
```
### 3. Activer la Configuration
```bash
# Créer le lien symbolique
sudo ln -s /etc/nginx/sites-available/certificator.4nkweb.com /etc/nginx/sites-enabled/
# Tester la configuration
sudo nginx -t
# Recharger nginx
sudo systemctl reload nginx
```
### 4. Obtenir le Certificat SSL
```bash
# Obtenir le certificat Let's Encrypt
sudo certbot --nginx -d certificator.4nkweb.com
# Vérifier le renouvellement automatique
sudo certbot renew --dry-run
```
## Vérification Post-Déploiement
### 1. Vérifier l'API Localement
```bash
# Sur le serveur de production
curl http://localhost:3010/health
```
### 2. Vérifier via le Proxy
```bash
# Depuis n'importe quelle machine
curl https://certificator.4nkweb.com/health
```
### 3. Tester l'Ancrage
```bash
# Utiliser le client de test
cd /srv/4NK/certificator.4nkweb.com
export API_KEY=prod-api-key-1
export API_URL=https://certificator.4nkweb.com
npm test
```
## Maintenance
### Redémarrage de l'API
```bash
# Via PM2
pm2 restart anchor-api
# Ou arrêter/démarrer
pm2 stop anchor-api
pm2 start anchor-api
```
### Mise à Jour
```bash
# Arrêter l'API
pm2 stop anchor-api
# Mettre à jour le code
cd /srv/4NK/certificator.4nkweb.com
git pull # ou copier les nouveaux fichiers
# Mettre à jour les dépendances si nécessaire
npm install --production
# Redémarrer
pm2 start anchor-api
```
### Logs
```bash
# Voir les logs en temps réel
pm2 logs anchor-api
# Voir les logs nginx
sudo tail -f /var/log/nginx/certificator.4nkweb.com.access.log
sudo tail -f /var/log/nginx/certificator.4nkweb.com.error.log
```
### Monitoring
```bash
# Statut PM2
pm2 status
# Informations détaillées
pm2 info anchor-api
# Monitoring en temps réel
pm2 monit
```
## Dépannage
### L'API ne répond pas
1. **Vérifier que l'API est démarrée** :
```bash
pm2 status
```
2. **Vérifier les logs** :
```bash
pm2 logs anchor-api --lines 50
```
3. **Vérifier le port** :
```bash
sudo netstat -tlnp | grep 3010
```
### Erreur de connexion Bitcoin RPC
1. **Vérifier que le nœud Bitcoin est accessible** :
```bash
curl -u bitcoin:bitcoin http://localhost:38332
```
2. **Vérifier les variables d'environnement** :
```bash
grep BITCOIN_RPC .env
```
### Erreur 502 Bad Gateway (Nginx)
1. **Vérifier que l'API est accessible depuis le proxy** :
```bash
# Depuis le proxy
curl http://192.168.1.103:3010/health
```
2. **Vérifier les logs nginx** :
```bash
sudo tail -f /var/log/nginx/certificator.4nkweb.com.error.log
```
3. **Vérifier le firewall** :
```bash
# Sur le serveur de production
sudo ufw status
# Le port 3010 doit être ouvert pour le réseau interne
```
## Sécurité
### Recommandations
1. **Clés API** :
- Générer des clés fortes (32+ caractères aléatoires)
- Rotater régulièrement
- Ne jamais les exposer publiquement
2. **RPC Bitcoin** :
- Utiliser des credentials différents en production
- Restreindre `RPCALLOWIP` dans bitcoin.conf
- Utiliser `rpcauth` au lieu de `rpcuser`/`rpcpassword`
3. **HTTPS** :
- Toujours utiliser HTTPS en production
- Renouveler automatiquement les certificats Let's Encrypt
4. **Firewall** :
- Ne pas exposer le port 3010 publiquement
- Utiliser nginx comme reverse proxy uniquement
5. **Logs** :
- Ne pas logger les clés API
- Surveiller les tentatives d'accès non autorisées
---
**Dernière mise à jour** : 2026-01-23

362
api-anchorage/README.md Normal file
View File

@ -0,0 +1,362 @@
# API d'Ancrage Bitcoin Signet
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0
## Description
API REST pour ancrer des documents sur la blockchain Bitcoin Signet. Cette API permet de créer des transactions Bitcoin qui incluent les hash de documents dans des outputs OP_RETURN, permettant ainsi de prouver l'existence d'un document à un moment donné.
## Caractéristiques
- **Port** : `3010`
- **Domaine** : `certificator.4nkweb.com` (via nginx sur proxy 192.168.1.100)
- **Authentification** : API Key via header `x-api-key`
- **Format** : JSON REST API
- **Bitcoin** : Connexion RPC au nœud Bitcoin Signet (port 38332)
- **Mempool** : Les transactions sont envoyées au mempool et retournées immédiatement (pas de callbacks)
## Installation
### Prérequis
- Node.js >= 18.0.0
- Accès au nœud Bitcoin Signet (RPC sur port 38332)
- Wallet Bitcoin avec des fonds pour créer des transactions
### Installation des Dépendances
```bash
cd api-anchorage
npm install
```
### Configuration
1. Copier le fichier d'exemple :
```bash
cp .env.example .env
```
2. Éditer `.env` :
```bash
# Bitcoin RPC Configuration
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
# API Configuration
API_PORT=3010
API_HOST=0.0.0.0
# API Keys (séparées par des virgules)
API_KEYS=your-api-key-here,another-api-key
# Logging
LOG_LEVEL=info
# Mining Configuration
MINING_ENABLED=true
MINING_FEE_RATE=0.00001
```
**Important** :
- `BITCOIN_RPC_HOST` : Si l'API est dans un conteneur Docker, utiliser l'IP du conteneur Bitcoin ou `host.docker.internal`
- `API_KEYS` : Définir au moins une clé API valide
## Démarrage
### Mode Développement
```bash
npm run dev
```
### Mode Production
```bash
npm start
```
### Avec PM2 (recommandé pour production)
```bash
pm2 start src/server.js --name anchor-api
pm2 save
pm2 startup
```
## Endpoints
### GET /health
Vérifie l'état de l'API et de la connexion Bitcoin.
**Authentification** : Non requise
**Réponse** :
```json
{
"ok": true,
"service": "anchor-api",
"bitcoin": {
"connected": true,
"blocks": 152321,
"chain": "signet",
"networkactive": true,
"connections": 1
},
"timestamp": "2026-01-23T16:35:27.821Z"
}
```
### POST /api/anchor/document
Ancre un document sur Bitcoin Signet. La transaction est créée et envoyée au mempool, puis retournée immédiatement.
**Authentification** : Requise (header `x-api-key`)
**Body** :
```json
{
"documentUid": "doc-123456",
"hash": "a1b2c3d4e5f6..."
}
```
**Note** : La transaction est envoyée au mempool et retournée immédiatement. Aucun callback n'est supporté.
**Réponse** :
```json
{
"txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85",
"status": "confirmed",
"confirmations": 0,
"block_height": 152321
}
```
**Erreurs** :
- `400 Bad Request` : Hash invalide
- `401 Unauthorized` : Clé API invalide ou manquante
- `402 Payment Required` : Solde insuffisant
- `500 Internal Server Error` : Erreur serveur
### POST /api/anchor/verify
Vérifie si un hash est ancré sur Bitcoin Signet.
**Authentification** : Requise (header `x-api-key`)
**Body** :
```json
{
"hash": "a1b2c3d4e5f6...",
"txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85" // optionnel
}
```
**Réponse** (hash trouvé) :
```json
{
"verified": true,
"anchor_info": {
"transaction_id": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85",
"block_height": 152321,
"confirmations": 0,
"current_block_height": 152321
}
}
```
**Réponse** (hash non trouvé) :
```json
{
"verified": false,
"message": "Hash not found in recent blocks"
}
```
## Utilisation
### Exemple avec curl
```bash
# Health check
curl http://localhost:3010/health
# Ancrer un document
curl -X POST http://localhost:3010/api/anchor/document \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key-here" \
-d '{
"documentUid": "doc-123",
"hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}'
# Vérifier un ancrage
curl -X POST http://localhost:3010/api/anchor/verify \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key-here" \
-d '{
"hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}'
```
### Exemple avec le client de test
```bash
# Configurer la clé API
export API_KEY=your-api-key-here
export API_URL=http://localhost:3010
# Exécuter le test
npm test
```
## Configuration Nginx (sur proxy 192.168.1.100)
Ajouter dans la configuration nginx du proxy :
```nginx
# API Anchor sur certificator.4nkweb.com
server {
listen 443 ssl http2;
server_name certificator.4nkweb.com;
ssl_certificate /etc/letsencrypt/live/certificator.4nkweb.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/certificator.4nkweb.com/privkey.pem;
location / {
proxy_pass http://192.168.1.103:3010;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
```
**Note** : Remplacer `192.168.1.103` par l'IP du serveur où l'API est déployée.
## Architecture
```
Client
↓ HTTPS
Nginx (proxy 192.168.1.100:443)
↓ HTTP
API Anchor (port 3010)
↓ RPC
Bitcoin Signet Node (port 38332)
```
## Sécurité
### Clés API
- Les clés API sont définies dans `.env` (variable `API_KEYS`)
- Plusieurs clés peuvent être définies (séparées par des virgules)
- La clé doit être envoyée dans le header `x-api-key`
- Le endpoint `/health` ne nécessite pas d'authentification
### Recommandations
1. **Ne jamais commiter `.env`** : Le fichier `.env` contient des secrets
2. **Utiliser HTTPS** : Via nginx avec certificats Let's Encrypt
3. **Restreindre RPCALLOWIP** : Dans la configuration Bitcoin, limiter l'accès RPC
4. **Rotater les clés API** : Changer régulièrement les clés API
5. **Monitoring** : Surveiller les logs pour détecter les abus
## Dépannage
### L'API ne peut pas se connecter à Bitcoin
**Vérifier** :
```bash
# Vérifier que le nœud Bitcoin est accessible
curl -u bitcoin:bitcoin http://localhost:38332
# Vérifier les variables d'environnement
grep BITCOIN_RPC .env
```
### Erreur "Insufficient balance"
Le wallet Bitcoin n'a pas assez de fonds pour créer des transactions.
**Solution** :
- Miner des blocs pour obtenir des récompenses
- Recevoir des fonds depuis un autre wallet
- Vérifier le solde : `bitcoin-cli getbalance`
### Erreur "Unauthorized"
La clé API est invalide ou manquante.
**Solution** :
- Vérifier que le header `x-api-key` est présent
- Vérifier que la clé est dans `API_KEYS` du `.env`
## Logs
Les logs sont affichés sur la console avec le format :
```
[2026-01-23T16:35:27.821Z] [INFO] API d'ancrage Bitcoin Signet démarrée {"host":"0.0.0.0","port":3010}
```
Niveaux de log :
- `error` : Erreurs critiques
- `warn` : Avertissements
- `info` : Informations générales
- `debug` : Détails de débogage
Contrôlé par la variable `LOG_LEVEL` dans `.env`.
## Structure du Projet
```
api-anchorage/
├── src/
│ ├── server.js # Serveur Express principal
│ ├── bitcoin-rpc.js # Client Bitcoin RPC
│ ├── logger.js # Système de logging
│ └── routes/
│ ├── health.js # Route health check
│ └── anchor.js # Routes d'ancrage
├── package.json
├── .env.example
├── .env # Configuration (ne pas commiter)
├── README.md
└── src/test-client.js # Client de test
```
## Développement
### Tests Locaux
```bash
# Démarrer l'API
npm run dev
# Dans un autre terminal, tester
npm test
```
### Format de Transaction
Les transactions d'ancrage utilisent un output OP_RETURN avec le format :
- Préfixe : `"ANCHOR:"` (7 bytes)
- Hash : Hash SHA256 du document (32 bytes)
Total : 39 bytes dans l'output OP_RETURN.
---
**Dernière mise à jour** : 2026-01-23

1602
api-anchorage/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
{
"name": "bitcoin-signet-anchor-api",
"version": "1.0.0",
"description": "API d'ancrage Bitcoin Signet pour certificator.4nkweb.com",
"main": "src/server.js",
"type": "module",
"scripts": {
"start": "node src/server.js",
"dev": "node --watch src/server.js",
"test": "node src/test-client.js"
},
"keywords": [
"bitcoin",
"signet",
"anchor",
"api"
],
"author": "Équipe 4NK",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"bitcoin-core": "^4.2.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@ -0,0 +1,374 @@
/**
* Client Bitcoin RPC
*
* Gère la connexion et les appels RPC vers le nœud Bitcoin Signet
*/
import Client from 'bitcoin-core';
import { logger } from './logger.js';
class BitcoinRPC {
constructor() {
this.client = new Client({
host: process.env.BITCOIN_RPC_HOST || 'localhost',
port: parseInt(process.env.BITCOIN_RPC_PORT || '38332'),
username: process.env.BITCOIN_RPC_USER || 'bitcoin',
password: process.env.BITCOIN_RPC_PASSWORD || 'bitcoin',
timeout: parseInt(process.env.BITCOIN_RPC_TIMEOUT || '30000'),
});
}
/**
* Vérifie la connexion au nœud Bitcoin
* @returns {Promise<Object>} Informations sur le nœud
*/
async checkConnection() {
try {
const networkInfo = await this.client.getNetworkInfo();
const blockchainInfo = await this.client.getBlockchainInfo();
return {
connected: true,
blocks: blockchainInfo.blocks,
chain: blockchainInfo.chain,
networkactive: networkInfo.networkactive,
connections: networkInfo.connections,
};
} catch (error) {
logger.error('Bitcoin RPC connection error', { error: error.message });
return {
connected: false,
error: error.message,
};
}
}
/**
* Obtient une nouvelle adresse depuis le wallet
* @returns {Promise<string>} Adresse Bitcoin
*/
async getNewAddress() {
try {
return await this.client.getNewAddress();
} catch (error) {
logger.error('Error getting new address', { error: error.message });
throw new Error(`Failed to get new address: ${error.message}`);
}
}
/**
* Obtient le solde du wallet
* @returns {Promise<number>} Solde en BTC
*/
async getBalance() {
try {
return await this.client.getBalance();
} catch (error) {
logger.error('Error getting balance', { error: error.message });
throw new Error(`Failed to get balance: ${error.message}`);
}
}
/**
* Crée une transaction d'ancrage
*
* @param {string} hash - Hash du document à ancrer (hex)
* @param {string} recipientAddress - Adresse de destination (optionnel, utilise getNewAddress si non fourni)
* @returns {Promise<Object>} Transaction créée avec txid
*/
async createAnchorTransaction(hash, recipientAddress = null) {
try {
// Vérifier que le hash est valide (64 caractères hex)
if (!/^[0-9a-fA-F]{64}$/.test(hash)) {
throw new Error('Invalid hash format. Must be 64 character hexadecimal string.');
}
// Obtenir une adresse de destination si non fournie
const address = recipientAddress || await this.getNewAddress();
// Obtenir le solde disponible
const balance = await this.getBalance();
const feeRate = parseFloat(process.env.MINING_FEE_RATE || '0.00001');
if (balance < feeRate) {
throw new Error(`Insufficient balance. Required: ${feeRate} BTC, Available: ${balance} BTC`);
}
// Créer une transaction avec le hash dans les données OP_RETURN
// Format: OP_RETURN + "ANCHOR:" + hash (32 bytes)
const hashBuffer = Buffer.from(hash, 'hex');
const anchorData = Buffer.concat([
Buffer.from('ANCHOR:', 'utf8'),
hashBuffer,
]);
// Obtenir les UTXOs disponibles (inclure les non confirmés pour avoir plus d'options)
// Utiliser fetch directement avec l'URL RPC incluant le wallet pour éviter les problèmes de wallet
const walletName = process.env.BITCOIN_RPC_WALLET || 'custom_signet';
const host = process.env.BITCOIN_RPC_HOST || 'localhost';
const port = process.env.BITCOIN_RPC_PORT || '38332';
const username = process.env.BITCOIN_RPC_USER || 'bitcoin';
const password = process.env.BITCOIN_RPC_PASSWORD || 'bitcoin';
const rpcUrl = `http://${host}:${port}/wallet/${walletName}`;
// Utiliser Basic Auth dans les headers (fetch ne supporte pas les credentials dans l'URL)
const auth = Buffer.from(`${username}:${password}`).toString('base64');
const rpcResponse = await fetch(rpcUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${auth}`,
},
body: JSON.stringify({
jsonrpc: '1.0',
id: 'listunspent',
method: 'listunspent',
params: [0],
}),
});
if (!rpcResponse.ok) {
const errorText = await rpcResponse.text();
logger.error('HTTP error in listunspent', { status: rpcResponse.status, statusText: rpcResponse.statusText, response: errorText });
throw new Error(`HTTP error fetching UTXOs: ${rpcResponse.status} ${rpcResponse.statusText}`);
}
const rpcResult = await rpcResponse.json();
if (rpcResult.error) {
logger.error('RPC error in listunspent', { error: rpcResult.error });
throw new Error(`RPC error: ${rpcResult.error.message}`);
}
const unspent = rpcResult.result;
logger.info('Fetched UTXOs', { count: unspent.length, firstFew: unspent.slice(0, 3).map(u => ({ txid: u.txid.substring(0, 16), vout: u.vout, amount: u.amount })) });
if (unspent.length === 0) {
throw new Error('No unspent outputs available');
}
// Log pour déboguer
logger.info('Available UTXOs', {
count: unspent.length,
amounts: unspent.map(u => u.amount).slice(0, 10),
largest: unspent.length > 0 ? Math.max(...unspent.map(u => u.amount)) : 0,
});
// Sélectionner un UTXO avec suffisamment de fonds
// Trier par montant décroissant pour prendre le plus grand UTXO disponible
const sortedUnspent = [...unspent].sort((a, b) => b.amount - a.amount);
const amount = 0.00001; // Montant minimal pour la transaction
const estimatedFee = 0.00005; // Estimation des frais (conservateur)
const totalNeeded = amount + estimatedFee;
// Trouver un UTXO avec suffisamment de fonds
let utxo = sortedUnspent.find(u => u.amount >= totalNeeded);
if (!utxo) {
// Si aucun UTXO n'est suffisant, utiliser le plus grand disponible
utxo = sortedUnspent[0];
logger.warn('Using largest available UTXO', {
required: totalNeeded,
available: utxo.amount,
allAmounts: sortedUnspent.map(u => u.amount).slice(0, 10),
});
if (utxo.amount < totalNeeded) {
throw new Error(`Insufficient UTXO amount. Required: ${totalNeeded} BTC, Largest available: ${utxo.amount} BTC. All UTXOs: ${sortedUnspent.map(u => u.amount).join(', ')}`);
}
}
// Créer la transaction raw avec les inputs et outputs (sans fundrawtransaction)
// Cela évite les erreurs de frais trop élevés avec la bibliothèque bitcoin-core
const inputs = [{
txid: utxo.txid,
vout: utxo.vout,
}];
const outputs = {
[address]: amount, // Montant minimal pour la transaction
data: anchorData.toString('hex'), // OP_RETURN output
};
const tx = await this.client.command('createrawtransaction', inputs, outputs);
// Signer la transaction
// Utiliser command() directement pour éviter les problèmes avec la bibliothèque
const signedTx = await this.client.command('signrawtransactionwithwallet', tx);
if (!signedTx.complete) {
throw new Error('Transaction signing failed');
}
// Envoyer la transaction au mempool
// Utiliser command() avec maxfeerate comme deuxième paramètre (0 = accepter n'importe quel taux)
// Le test direct avec bitcoin-cli fonctionne avec cette syntaxe
const txid = await this.client.command('sendrawtransaction', signedTx.hex, 0);
logger.info('Anchor transaction sent to mempool', {
txid,
hash: hash.substring(0, 16) + '...',
address,
});
// Obtenir les informations de la transaction (dans le mempool)
const txInfo = await this.getTransactionInfo(txid);
return {
txid,
status: 'confirmed', // Transaction dans le mempool
confirmations: txInfo.confirmations || 0,
block_height: txInfo.blockheight || null, // null si pas encore dans un bloc
};
} catch (error) {
logger.error('Error creating anchor transaction', {
error: error.message,
hash: hash?.substring(0, 16) + '...',
});
throw error;
}
}
/**
* Obtient les informations d'une transaction
* @param {string} txid - ID de la transaction
* @returns {Promise<Object>} Informations de la transaction
*/
async getTransactionInfo(txid) {
try {
const tx = await this.client.getTransaction(txid);
const blockchainInfo = await this.client.getBlockchainInfo();
return {
txid: tx.txid,
confirmations: tx.confirmations || 0,
blockheight: tx.blockheight || null,
blockhash: tx.blockhash || null,
time: tx.time || null,
currentBlockHeight: blockchainInfo.blocks,
};
} catch (error) {
logger.error('Error getting transaction info', { error: error.message, txid });
throw new Error(`Failed to get transaction info: ${error.message}`);
}
}
/**
* Vérifie si un hash est ancré dans la blockchain
*
* @param {string} hash - Hash à vérifier
* @param {string} txid - ID de transaction optionnel pour accélérer la recherche
* @returns {Promise<Object>} Résultat de la vérification
*/
async verifyAnchor(hash, txid = null) {
try {
// Vérifier que le hash est valide
if (!/^[0-9a-fA-F]{64}$/.test(hash)) {
throw new Error('Invalid hash format. Must be 64 character hexadecimal string.');
}
// Si un txid est fourni, vérifier directement cette transaction
if (txid) {
try {
const tx = await this.client.getTransaction(txid, true);
const rawTx = await this.client.getRawTransaction(txid, true);
// Vérifier si le hash est dans les outputs OP_RETURN
const hashFound = this.checkHashInTransaction(rawTx, hash);
if (hashFound) {
return {
verified: true,
anchor_info: {
transaction_id: txid,
block_height: tx.blockheight || null,
confirmations: tx.confirmations || 0,
},
};
}
} catch (error) {
// Si la transaction n'existe pas, continuer la recherche
logger.warn('Transaction not found, searching blockchain', { txid, error: error.message });
}
}
// Rechercher dans les blocs récents (derniers 100 blocs)
const blockchainInfo = await this.client.getBlockchainInfo();
const currentHeight = blockchainInfo.blocks;
const searchRange = 100; // Rechercher dans les 100 derniers blocs
for (let height = currentHeight; height >= Math.max(0, currentHeight - searchRange); height--) {
try {
const blockHash = await this.client.getBlockHash(height);
const block = await this.client.getBlock(blockHash, 2); // Verbose level 2
// Parcourir toutes les transactions du bloc
for (const tx of block.tx || []) {
try {
const rawTx = await this.client.getRawTransaction(tx.txid, true);
const hashFound = this.checkHashInTransaction(rawTx, hash);
if (hashFound) {
return {
verified: true,
anchor_info: {
transaction_id: tx.txid,
block_height: height,
confirmations: currentHeight - height + 1,
},
};
}
} catch (error) {
// Continuer avec la transaction suivante
logger.debug('Error checking transaction', { txid: tx.txid, error: error.message });
}
}
} catch (error) {
// Continuer avec le bloc suivant
logger.debug('Error checking block', { height, error: error.message });
}
}
// Hash non trouvé
return {
verified: false,
message: 'Hash not found in recent blocks',
};
} catch (error) {
logger.error('Error verifying anchor', { error: error.message, hash: hash?.substring(0, 16) + '...' });
throw error;
}
}
/**
* Vérifie si un hash est présent dans une transaction
* @param {Object} rawTx - Transaction brute
* @param {string} hash - Hash à rechercher
* @returns {boolean} True si le hash est trouvé
*/
checkHashInTransaction(rawTx, hash) {
try {
// Parcourir les outputs de la transaction
for (const output of rawTx.vout || []) {
// Chercher dans les scripts OP_RETURN
if (output.scriptPubKey && output.scriptPubKey.hex) {
const scriptHex = output.scriptPubKey.hex;
// Vérifier si le script contient "ANCHOR:" suivi du hash
const anchorPrefix = Buffer.from('ANCHOR:', 'utf8').toString('hex');
const hashHex = hash.toLowerCase();
if (scriptHex.includes(anchorPrefix + hashHex)) {
return true;
}
}
}
return false;
} catch (error) {
logger.error('Error checking hash in transaction', { error: error.message });
return false;
}
}
}
// Export class and singleton
export { BitcoinRPC };
export const bitcoinRPC = new BitcoinRPC();

View File

@ -0,0 +1,41 @@
/**
* Logger simple
*/
const LOG_LEVELS = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
const currentLevel = LOG_LEVELS[process.env.LOG_LEVEL || 'info'] || LOG_LEVELS.info;
function formatMessage(level, message, meta = {}) {
const timestamp = new Date().toISOString();
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
return `[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`;
}
export const logger = {
error: (message, meta = {}) => {
if (currentLevel >= LOG_LEVELS.error) {
console.error(formatMessage('error', message, meta));
}
},
warn: (message, meta = {}) => {
if (currentLevel >= LOG_LEVELS.warn) {
console.warn(formatMessage('warn', message, meta));
}
},
info: (message, meta = {}) => {
if (currentLevel >= LOG_LEVELS.info) {
console.log(formatMessage('info', message, meta));
}
},
debug: (message, meta = {}) => {
if (currentLevel >= LOG_LEVELS.debug) {
console.log(formatMessage('debug', message, meta));
}
},
};

View File

@ -0,0 +1,117 @@
/**
* Routes d'ancrage
*/
import express from 'express';
import { bitcoinRPC } from '../bitcoin-rpc.js';
import { logger } from '../logger.js';
export const anchorRouter = express.Router();
/**
* POST /api/anchor/document
* Ancre un document sur Bitcoin Signet
*
* La transaction est envoyée au mempool et retournée immédiatement.
*
* Body:
* - documentUid: string (identifiant du document)
* - hash: string (hash SHA256 du document en hex, 64 caractères)
*/
anchorRouter.post('/document', async (req, res) => {
try {
const { documentUid, hash } = req.body;
// Validation
if (!hash) {
return res.status(400).json({
error: 'Bad Request',
message: 'hash is required',
});
}
if (!/^[0-9a-fA-F]{64}$/.test(hash)) {
return res.status(400).json({
error: 'Bad Request',
message: 'hash must be a 64 character hexadecimal string',
});
}
logger.info('Anchor request received', {
documentUid: documentUid || 'unknown',
hash: hash.substring(0, 16) + '...',
});
// Créer la transaction d'ancrage et l'envoyer au mempool
const result = await bitcoinRPC.createAnchorTransaction(hash);
// Retourner le résultat immédiatement (transaction dans le mempool)
res.status(200).json({
txid: result.txid,
status: result.status,
confirmations: result.confirmations,
block_height: result.block_height,
});
} catch (error) {
logger.error('Anchor error', { error: error.message, stack: error.stack });
// Déterminer le code de statut approprié
let statusCode = 500;
if (error.message.includes('Insufficient balance')) {
statusCode = 402; // Payment Required
} else if (error.message.includes('Invalid')) {
statusCode = 400; // Bad Request
}
res.status(statusCode).json({
error: error.message.includes('Insufficient balance') ? 'Insufficient Balance' : 'Internal Server Error',
message: error.message,
});
}
});
/**
* POST /api/anchor/verify
* Vérifie si un hash est ancré sur Bitcoin Signet
*
* Body:
* - hash: string (hash SHA256 à vérifier, 64 caractères hex)
* - txid: string (optionnel, ID de transaction pour accélérer la recherche)
*/
anchorRouter.post('/verify', async (req, res) => {
try {
const { hash, txid } = req.body;
// Validation
if (!hash) {
return res.status(400).json({
error: 'Bad Request',
message: 'hash is required',
});
}
if (!/^[0-9a-fA-F]{64}$/.test(hash)) {
return res.status(400).json({
error: 'Bad Request',
message: 'hash must be a 64 character hexadecimal string',
});
}
logger.info('Verify request received', {
hash: hash.substring(0, 16) + '...',
txid: txid ? txid.substring(0, 16) + '...' : 'none',
});
// Vérifier l'ancrage
const result = await bitcoinRPC.verifyAnchor(hash, txid);
res.status(200).json(result);
} catch (error) {
logger.error('Verify error', { error: error.message, stack: error.stack });
res.status(500).json({
error: 'Internal Server Error',
message: error.message,
});
}
});

View File

@ -0,0 +1,40 @@
/**
* Route Health Check
*/
import express from 'express';
import { bitcoinRPC } from '../bitcoin-rpc.js';
import { logger } from '../logger.js';
export const healthRouter = express.Router();
/**
* GET /health
* Vérifie l'état de l'API et de la connexion Bitcoin
*/
healthRouter.get('/', async (req, res) => {
try {
const bitcoinStatus = await bitcoinRPC.checkConnection();
const health = {
ok: bitcoinStatus.connected,
service: 'anchor-api',
bitcoin: {
connected: bitcoinStatus.connected,
blocks: bitcoinStatus.blocks || 0,
},
timestamp: new Date().toISOString(),
};
const statusCode = bitcoinStatus.connected ? 200 : 503;
res.status(statusCode).json(health);
} catch (error) {
logger.error('Health check error', { error: error.message });
res.status(503).json({
ok: false,
service: 'anchor-api',
error: error.message,
timestamp: new Date().toISOString(),
});
}
});

121
api-anchorage/src/server.js Normal file
View File

@ -0,0 +1,121 @@
#!/usr/bin/env node
/**
* API d'Ancrage Bitcoin Signet
*
* Cette API permet d'ancrer des documents sur la blockchain Bitcoin Signet
* en créant des transactions qui incluent les hash des documents.
*
* Port: 3010
* Domaine: certificator.4nkweb.com
*/
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { BitcoinRPC } from './bitcoin-rpc.js';
import { anchorRouter } from './routes/anchor.js';
import { healthRouter } from './routes/health.js';
import { logger } from './logger.js';
// Charger les variables d'environnement
dotenv.config();
const app = express();
const PORT = process.env.API_PORT || 3010;
const HOST = process.env.API_HOST || '0.0.0.0';
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Middleware de logging
app.use((req, res, next) => {
logger.info(`${req.method} ${req.path}`, {
ip: req.ip,
userAgent: req.get('user-agent'),
});
next();
});
// Middleware d'authentification API Key
app.use((req, res, next) => {
// Exclure /health de l'authentification
if (req.path === '/health' || req.path === '/') {
return next();
}
const apiKey = req.headers['x-api-key'];
const validKeys = process.env.API_KEYS?.split(',').map(k => k.trim()) || [];
if (!apiKey || !validKeys.includes(apiKey)) {
logger.warn('Unauthorized API access attempt', { ip: req.ip, path: req.path });
return res.status(401).json({
error: 'Unauthorized',
message: 'Invalid or missing API key',
});
}
next();
});
// Routes
app.use('/health', healthRouter);
app.use('/api/anchor', anchorRouter);
// Route racine
app.get('/', (req, res) => {
res.json({
service: 'bitcoin-signet-anchor-api',
version: '1.0.0',
endpoints: {
health: '/health',
anchor: '/api/anchor/document',
verify: '/api/anchor/verify',
},
});
});
// Gestion des erreurs
app.use((err, req, res, next) => {
logger.error('Unhandled error', { error: err.message, stack: err.stack });
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : 'An error occurred',
});
});
// Gestion des routes non trouvées
app.use((req, res) => {
res.status(404).json({
error: 'Not Found',
message: `Route ${req.method} ${req.path} not found`,
});
});
// Démarrage du serveur
const server = app.listen(PORT, HOST, () => {
logger.info(`API d'ancrage Bitcoin Signet démarrée`, {
host: HOST,
port: PORT,
environment: process.env.NODE_ENV || 'production',
});
});
// Gestion de l'arrêt propre
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down gracefully');
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down gracefully');
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
});

View File

@ -0,0 +1,197 @@
#!/usr/bin/env node
/**
* Client de test pour l'API d'ancrage
* Usage: node src/test-client.js
*/
import https from 'https';
import http from 'http';
import crypto from 'crypto';
// Configuration
const API_URL = process.env.API_URL || 'http://localhost:3010';
const API_KEY = process.env.API_KEY || 'your-api-key-here';
/**
* Effectue une requête HTTP/HTTPS
*/
function makeRequest(method, path, data = null) {
return new Promise((resolve, reject) => {
const url = new URL(path, API_URL);
const isHttps = url.protocol === 'https:';
const httpModule = isHttps ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname,
method: method,
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
};
const req = httpModule.request(options, (res) => {
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
try {
const json = JSON.parse(body);
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.error || body}`));
}
} catch (e) {
reject(new Error(`Parse error: ${e.message}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
if (data) {
req.write(JSON.stringify(data));
}
req.end();
});
}
/**
* Vérifie l'état de l'API
*/
async function checkHealth() {
try {
console.log('🔍 Vérification de l\'état de l\'API...');
const response = await makeRequest('GET', '/health');
console.log('✅ API opérationnelle');
console.log(` Bitcoin connecté: ${response.bitcoin.connected ? 'Oui' : 'Non'}`);
console.log(` Blocs: ${response.bitcoin.blocks}`);
return response;
} catch (error) {
console.error('❌ Erreur:', error.message);
throw error;
}
}
/**
* Ancre un document sur Bitcoin Signet
* La transaction est envoyée au mempool et retournée immédiatement.
*/
async function anchorDocument(documentUid, hash) {
try {
console.log(`\n📎 Ancrage du document: ${documentUid}`);
console.log(` Hash: ${hash.substring(0, 16)}...`);
const data = {
documentUid,
hash,
};
const response = await makeRequest('POST', '/api/anchor/document', data);
console.log('✅ Document ancré avec succès');
console.log(` Transaction ID: ${response.txid}`);
console.log(` Confirmations: ${response.confirmations}`);
if (response.block_height) {
console.log(` Block height: ${response.block_height}`);
}
return response;
} catch (error) {
console.error('❌ Erreur lors de l\'ancrage:', error.message);
throw error;
}
}
/**
* Vérifie si un hash est ancré
*/
async function verifyAnchor(hash, txid = null) {
try {
console.log(`\n🔍 Vérification de l'ancrage...`);
console.log(` Hash: ${hash.substring(0, 16)}...`);
const data = { hash };
if (txid) {
data.txid = txid;
console.log(` Transaction ID: ${txid.substring(0, 16)}...`);
}
const response = await makeRequest('POST', '/api/anchor/verify', data);
if (response.verified) {
console.log('✅ Hash vérifié et ancré');
if (response.anchor_info) {
console.log(` Transaction ID: ${response.anchor_info.transaction_id}`);
console.log(` Block height: ${response.anchor_info.block_height}`);
console.log(` Confirmations: ${response.anchor_info.confirmations}`);
}
} else {
console.log('❌ Hash non trouvé dans la blockchain');
}
return response;
} catch (error) {
console.error('❌ Erreur lors de la vérification:', error.message);
throw error;
}
}
/**
* Fonction principale
*/
async function main() {
console.log('=== Client API Anchor ===\n');
try {
// 1. Vérifier l'état de l'API
await checkHealth();
// 2. Générer un hash de test
const documentContent = `Document de test - ${new Date().toISOString()}`;
const hash = crypto.createHash('sha256').update(documentContent).digest('hex');
const documentUid = `doc-${Date.now()}`;
console.log(`\n📄 Document de test:`);
console.log(` UID: ${documentUid}`);
console.log(` Hash: ${hash}`);
// 3. Ancrer le document
const anchorResult = await anchorDocument(documentUid, hash);
// 4. Attendre un peu pour que la transaction soit propagée
console.log('\n⏳ Attente de 3 secondes pour la propagation...');
await new Promise((resolve) => setTimeout(resolve, 3000));
// 5. Vérifier l'ancrage
await verifyAnchor(hash, anchorResult.txid);
console.log('\n✅ Tous les tests sont passés avec succès!');
} catch (error) {
console.error('\n❌ Erreur:', error.message);
process.exit(1);
}
}
// Exécuter le script
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
// Exports pour utilisation comme module
export {
checkHealth,
anchorDocument,
verifyAnchor,
};

17
api-faucet/.env.example Normal file
View File

@ -0,0 +1,17 @@
# Bitcoin RPC Configuration
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
BITCOIN_RPC_TIMEOUT=30000
# API Configuration
FAUCET_API_PORT=3021
FAUCET_API_HOST=0.0.0.0
# Faucet Configuration
FAUCET_AMOUNT=0.0005 # Montant en BTC (50000 sats par défaut)
# Logging
LOG_LEVEL=info
NODE_ENV=production

6
api-faucet/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/
.env
*.log
.DS_Store
dist/
build/

306
api-faucet/README.md Normal file
View File

@ -0,0 +1,306 @@
# API Faucet Bitcoin Signet
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0.0
## Description
API REST pour distribuer des sats (50000 sats = 0.0005 BTC) sur la blockchain Bitcoin Signet. Cette API permet aux utilisateurs de recevoir des fonds de test pour développer et tester des applications Bitcoin.
## Caractéristiques
- **Port** : `3021`
- **Format** : JSON REST API
- **Bitcoin** : Connexion RPC au nœud Bitcoin Signet (port 38332)
- **Montant par défaut** : 50 000 sats (0.0005 BTC)
## Installation
### Prérequis
- Node.js >= 18.0.0
- Accès au nœud Bitcoin Signet (RPC sur port 38332)
- Wallet Bitcoin avec des fonds pour distribuer
### Installation des Dépendances
```bash
cd api-faucet
npm install
```
### Configuration
1. Copier le fichier d'exemple :
```bash
cp .env.example .env
```
2. Éditer `.env` :
```bash
# Bitcoin RPC Configuration
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
BITCOIN_RPC_TIMEOUT=30000
# API Configuration
FAUCET_API_PORT=3021
FAUCET_API_HOST=0.0.0.0
# Faucet Configuration
FAUCET_AMOUNT=0.0005 # Montant en BTC (50000 sats par défaut)
# Logging
LOG_LEVEL=info
NODE_ENV=production
```
**Important** :
- `BITCOIN_RPC_HOST` : Si l'API est dans un conteneur Docker, utiliser l'IP du conteneur Bitcoin ou `host.docker.internal`
- `FAUCET_AMOUNT` : Montant à distribuer en BTC (par défaut 0.0005 = 50000 sats)
## Démarrage
### Mode Développement
```bash
npm run dev
```
### Mode Production
```bash
npm start
```
### Avec PM2 (recommandé pour production)
```bash
# Installer PM2 globalement
sudo npm install -g pm2
# Démarrer l'API avec PM2
pm2 start src/server.js --name faucet-api
# Sauvegarder la configuration PM2
pm2 save
# Configurer PM2 pour démarrer au boot
pm2 startup
# Suivre les instructions affichées
```
## Endpoints
### GET /health
Vérifie l'état de l'API et de la connexion Bitcoin.
**Authentification** : Non requise
**Réponse** :
```json
{
"status": "ok",
"service": "bitcoin-signet-faucet-api",
"version": "1.0.0",
"bitcoin": {
"connected": true,
"chain": "signet",
"blocks": 1234,
"networkactive": true,
"connections": 5,
"wallet_balance": 10.5
},
"timestamp": "2026-01-23T12:00:00.000Z"
}
```
### POST /api/faucet/request
Demande des sats via le faucet.
**Authentification** : Non requise
**Body** :
```json
{
"address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc"
}
```
**Réponse (succès)** :
```json
{
"success": true,
"txid": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890",
"address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc",
"amount": 0.0005,
"amount_sats": 50000,
"status": "pending",
"confirmations": 0,
"block_height": null
}
```
**Réponse (erreur)** :
```json
{
"error": "Bad Request",
"message": "Invalid Bitcoin address format"
}
```
## Exemples d'Utilisation
### Avec curl
```bash
# Vérifier l'état de l'API
curl http://localhost:3021/health
# Demander des sats
curl -X POST http://localhost:3021/api/faucet/request \
-H "Content-Type: application/json" \
-d '{
"address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc"
}'
```
### Avec JavaScript (fetch)
```javascript
const response = await fetch('http://localhost:3021/api/faucet/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
address: 'tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc',
}),
});
const result = await response.json();
console.log(result);
```
## Configuration Nginx (sur proxy 192.168.1.100)
Ajouter dans la configuration nginx du proxy :
```nginx
# API Faucet
server {
listen 443 ssl http2;
server_name faucet.signet.4nkweb.com;
ssl_certificate /etc/letsencrypt/live/faucet.signet.4nkweb.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/faucet.signet.4nkweb.com/privkey.pem;
location / {
proxy_pass http://192.168.1.103:3021;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
```
**Note** : Remplacer `192.168.1.103` par l'IP du serveur où l'API est déployée.
## Architecture
```
Client
↓ HTTPS
Nginx (proxy 192.168.1.100:443)
↓ HTTP
API Faucet (port 3021)
↓ RPC
Bitcoin Signet Node (port 38332)
```
## Sécurité
### Rate Limiting (Recommandé)
Pour éviter les abus, il est recommandé d'implémenter un rate limiting. Exemple avec `express-rate-limit` :
```bash
npm install express-rate-limit
```
```javascript
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 1, // 1 requête par adresse IP toutes les 15 minutes
message: 'Too many requests from this IP, please try again later.',
});
app.use('/api/faucet/request', limiter);
```
### Validation des Adresses
L'API valide automatiquement les adresses Bitcoin avant d'envoyer les fonds. Seules les adresses valides sont acceptées.
### Gestion des Erreurs
- **400 Bad Request** : Adresse invalide
- **503 Service Unavailable** : Solde insuffisant
- **500 Internal Server Error** : Erreur serveur
## Dépannage
### L'API ne démarre pas
- Vérifier que le port 3021 n'est pas utilisé : `netstat -tlnp | grep 3021`
- Vérifier les logs : `pm2 logs faucet-api`
- Vérifier la configuration dans `.env`
### Erreur "Insufficient balance"
- Vérifier le solde du wallet : `bitcoin-cli getbalance`
- Miner des blocs pour obtenir des fonds
- Vérifier que le wallet est déverrouillé
### Erreur "Invalid Bitcoin address"
- Vérifier que l'adresse est une adresse Signet valide (commence par `tb1`, `bcrt1`, `2`, ou `3`)
- Vérifier le format de l'adresse (25-62 caractères)
### Transaction non envoyée
- Vérifier les logs de l'API pour les erreurs RPC
- Vérifier que le nœud Bitcoin est accessible
- Vérifier que le wallet est déverrouillé et a des fonds
## Structure des Fichiers
```
api-faucet/
├── package.json
├── README.md
├── .env.example
└── src/
├── server.js # Serveur Express
├── bitcoin-rpc.js # Client Bitcoin RPC
├── logger.js # Logger
└── routes/
├── faucet.js # Routes du faucet
└── health.js # Routes de santé
```
## Licence
MIT

1602
api-faucet/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
api-faucet/package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "bitcoin-signet-faucet-api",
"version": "1.0.0",
"description": "API faucet pour Bitcoin Signet",
"main": "src/server.js",
"type": "module",
"scripts": {
"start": "node src/server.js",
"dev": "node --watch src/server.js"
},
"keywords": [
"bitcoin",
"signet",
"faucet",
"api"
],
"author": "Équipe 4NK",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"bitcoin-core": "^4.2.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@ -0,0 +1,136 @@
/**
* Client Bitcoin RPC pour le faucet
*
* Gère la connexion et les appels RPC vers le nœud Bitcoin Signet
*/
import Client from 'bitcoin-core';
import { logger } from './logger.js';
class BitcoinRPC {
constructor() {
this.client = new Client({
host: process.env.BITCOIN_RPC_HOST || 'localhost',
port: parseInt(process.env.BITCOIN_RPC_PORT || '38332'),
username: process.env.BITCOIN_RPC_USER || 'bitcoin',
password: process.env.BITCOIN_RPC_PASSWORD || 'bitcoin',
timeout: parseInt(process.env.BITCOIN_RPC_TIMEOUT || '30000'),
});
}
/**
* Vérifie la connexion au nœud Bitcoin
* @returns {Promise<Object>} Informations sur le nœud
*/
async checkConnection() {
try {
const networkInfo = await this.client.getNetworkInfo();
const blockchainInfo = await this.client.getBlockchainInfo();
return {
connected: true,
blocks: blockchainInfo.blocks,
chain: blockchainInfo.chain,
networkactive: networkInfo.networkactive,
connections: networkInfo.connections,
};
} catch (error) {
logger.error('Bitcoin RPC connection error', { error: error.message });
return {
connected: false,
error: error.message,
};
}
}
/**
* Obtient le solde du wallet
* @returns {Promise<number>} Solde en BTC
*/
async getBalance() {
try {
return await this.client.getBalance();
} catch (error) {
logger.error('Error getting balance', { error: error.message });
throw new Error(`Failed to get balance: ${error.message}`);
}
}
/**
* Envoie des sats à une adresse
* @param {string} address - Adresse Bitcoin de destination
* @param {number} amount - Montant en BTC (par défaut 0.0005 BTC = 50000 sats)
* @returns {Promise<Object>} Transaction créée avec txid
*/
async sendToAddress(address, amount = 0.0005) {
try {
// Vérifier que l'adresse est valide
const validateResult = await this.client.validateAddress(address);
if (!validateResult.isvalid) {
throw new Error('Invalid Bitcoin address');
}
// Vérifier le solde disponible
const balance = await this.client.getBalance();
if (balance < amount) {
throw new Error(`Insufficient balance. Required: ${amount} BTC, Available: ${balance} BTC`);
}
// Envoyer les sats
const txid = await this.client.sendToAddress(address, amount);
logger.info('Faucet transaction sent', {
txid,
address,
amount,
});
// Obtenir les informations de la transaction
const txInfo = await this.getTransactionInfo(txid);
return {
txid,
address,
amount,
amount_sats: Math.round(amount * 100000000),
status: 'pending',
confirmations: txInfo.confirmations || 0,
block_height: txInfo.blockheight || null,
};
} catch (error) {
logger.error('Error sending to address', {
error: error.message,
address,
amount,
});
throw error;
}
}
/**
* Obtient les informations d'une transaction
* @param {string} txid - ID de la transaction
* @returns {Promise<Object>} Informations de la transaction
*/
async getTransactionInfo(txid) {
try {
const tx = await this.client.getTransaction(txid);
const blockchainInfo = await this.client.getBlockchainInfo();
return {
txid: tx.txid,
confirmations: tx.confirmations || 0,
blockheight: tx.blockheight || null,
blockhash: tx.blockhash || null,
time: tx.time || null,
currentBlockHeight: blockchainInfo.blocks,
};
} catch (error) {
logger.error('Error getting transaction info', { error: error.message, txid });
throw new Error(`Failed to get transaction info: ${error.message}`);
}
}
}
// Export singleton
export const bitcoinRPC = new BitcoinRPC();

30
api-faucet/src/logger.js Normal file
View File

@ -0,0 +1,30 @@
/**
* Logger simple pour l'API faucet
*/
const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
const levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
function log(level, message, meta = {}) {
const levelNum = levels[level] || 2;
const currentLevel = levels[LOG_LEVEL] || 2;
if (levelNum <= currentLevel) {
const timestamp = new Date().toISOString();
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`);
}
}
export const logger = {
error: (message, meta) => log('error', message, meta),
warn: (message, meta) => log('warn', message, meta),
info: (message, meta) => log('info', message, meta),
debug: (message, meta) => log('debug', message, meta),
};

View File

@ -0,0 +1,75 @@
/**
* Routes du faucet
*/
import express from 'express';
import { bitcoinRPC } from '../bitcoin-rpc.js';
import { logger } from '../logger.js';
export const faucetRouter = express.Router();
/**
* POST /api/faucet/request
* Demande des sats via le faucet
*
* Body:
* - address: string (adresse Bitcoin Signet valide)
*/
faucetRouter.post('/request', async (req, res) => {
try {
const { address } = req.body;
// Validation
if (!address) {
return res.status(400).json({
error: 'Bad Request',
message: 'address is required',
});
}
// Validation basique de l'adresse
const addressPattern = /^(tb1|bcrt1|2|3)[a-zA-HJ-NP-Z0-9]{25,62}$/;
if (!addressPattern.test(address)) {
return res.status(400).json({
error: 'Bad Request',
message: 'Invalid Bitcoin address format',
});
}
logger.info('Faucet request received', {
address: address.substring(0, 10) + '...',
});
// Montant par défaut : 50000 sats = 0.0005 BTC
const amount = parseFloat(process.env.FAUCET_AMOUNT || '0.0005');
// Envoyer les sats
const result = await bitcoinRPC.sendToAddress(address, amount);
res.status(200).json({
success: true,
txid: result.txid,
address: result.address,
amount: result.amount,
amount_sats: result.amount_sats,
status: result.status,
confirmations: result.confirmations,
block_height: result.block_height,
});
} catch (error) {
logger.error('Faucet error', { error: error.message, stack: error.stack });
// Déterminer le code de statut approprié
let statusCode = 500;
if (error.message.includes('Insufficient balance')) {
statusCode = 503; // Service Unavailable
} else if (error.message.includes('Invalid')) {
statusCode = 400; // Bad Request
}
res.status(statusCode).json({
error: error.message.includes('Insufficient balance') ? 'Insufficient Balance' : 'Internal Server Error',
message: error.message,
});
}
});

View File

@ -0,0 +1,43 @@
/**
* Routes de santé
*/
import express from 'express';
import { bitcoinRPC } from '../bitcoin-rpc.js';
import { logger } from '../logger.js';
export const healthRouter = express.Router();
/**
* GET /health
* Vérifie l'état de l'API et de la connexion Bitcoin
*/
healthRouter.get('/', async (req, res) => {
try {
const connection = await bitcoinRPC.checkConnection();
const balance = await bitcoinRPC.getBalance();
res.status(200).json({
status: 'ok',
service: 'bitcoin-signet-faucet-api',
version: '1.0.0',
bitcoin: {
connected: connection.connected,
chain: connection.chain,
blocks: connection.blocks,
networkactive: connection.networkactive,
connections: connection.connections,
wallet_balance: balance,
},
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error('Health check error', { error: error.message });
res.status(503).json({
status: 'error',
service: 'bitcoin-signet-faucet-api',
error: error.message,
timestamp: new Date().toISOString(),
});
}
});

98
api-faucet/src/server.js Normal file
View File

@ -0,0 +1,98 @@
#!/usr/bin/env node
/**
* API Faucet Bitcoin Signet
*
* Cette API permet de distribuer des sats (50000 sats = 0.0005 BTC)
* sur la blockchain Bitcoin Signet.
*
* Port: 3021
*/
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { bitcoinRPC } from './bitcoin-rpc.js';
import { faucetRouter } from './routes/faucet.js';
import { healthRouter } from './routes/health.js';
import { logger } from './logger.js';
// Charger les variables d'environnement
dotenv.config();
const app = express();
const PORT = process.env.FAUCET_API_PORT || 3021;
const HOST = process.env.FAUCET_API_HOST || '0.0.0.0';
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Middleware de logging
app.use((req, res, next) => {
logger.info(`${req.method} ${req.path}`, {
ip: req.ip,
userAgent: req.get('user-agent'),
});
next();
});
// Routes
app.use('/health', healthRouter);
app.use('/api/faucet', faucetRouter);
// Route racine
app.get('/', (req, res) => {
res.json({
service: 'bitcoin-signet-faucet-api',
version: '1.0.0',
endpoints: {
health: '/health',
request: '/api/faucet/request',
},
});
});
// Gestion des erreurs
app.use((err, req, res, next) => {
logger.error('Unhandled error', { error: err.message, stack: err.stack });
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : 'An error occurred',
});
});
// Gestion des routes non trouvées
app.use((req, res) => {
res.status(404).json({
error: 'Not Found',
message: `Route ${req.method} ${req.path} not found`,
});
});
// Démarrage du serveur
const server = app.listen(PORT, HOST, () => {
logger.info(`API Faucet Bitcoin Signet démarrée`, {
host: HOST,
port: PORT,
environment: process.env.NODE_ENV || 'production',
});
});
// Gestion de l'arrêt propre
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down gracefully');
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down gracefully');
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
});

19
api-faucet/start.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
cd "$(dirname "$0")"
export BITCOIN_RPC_HOST=${BITCOIN_RPC_HOST:-localhost}
export BITCOIN_RPC_PORT=${BITCOIN_RPC_PORT:-38332}
export BITCOIN_RPC_USER=${BITCOIN_RPC_USER:-bitcoin}
export BITCOIN_RPC_PASSWORD=${BITCOIN_RPC_PASSWORD:-bitcoin}
export FAUCET_API_PORT=${FAUCET_API_PORT:-3021}
export FAUCET_API_HOST=${FAUCET_API_HOST:-0.0.0.0}
export FAUCET_AMOUNT=${FAUCET_AMOUNT:-0.0005}
export LOG_LEVEL=${LOG_LEVEL:-info}
export NODE_ENV=${NODE_ENV:-production}
if [ ! -f node_modules/.bin/node ]; then
echo "Installation des dépendances..."
npm install
fi
echo "Démarrage de l'API Faucet sur le port $FAUCET_API_PORT..."
node src/server.js

234
configure-nginx-proxy.sh Executable file
View File

@ -0,0 +1,234 @@
#!/bin/bash
# Script de configuration Nginx pour les sous-domaines certificator.4nkweb.com
# Usage: ./configure-nginx-proxy.sh
set -e
PROXY_HOST="192.168.1.100"
PROXY_USER="ncantu"
NGINX_SITES_AVAILABLE="/etc/nginx/sites-available"
NGINX_SITES_ENABLED="/etc/nginx/sites-enabled"
CERTBOT_BIN="/usr/bin/certbot"
echo "=== Configuration Nginx pour certificator.4nkweb.com ==="
echo ""
# Vérifier que nous sommes sur le proxy ou que nous pouvons y accéder
# Note: Le script peut être exécuté localement ou via SSH
CURRENT_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "")
if [ "$CURRENT_IP" != "192.168.1.100" ] && [ -z "$SSH_CONNECTION" ]; then
echo " Ce script peut être exécuté sur le proxy (192.168.1.100)"
echo " Ou via SSH: ssh ${PROXY_USER}@${PROXY_HOST} 'sudo bash -s' < $0"
echo ""
fi
# Vérifier les permissions (sudo disponible pour ncantu)
if [ "$EUID" -ne 0 ]; then
if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
echo "✅ Utilisation de sudo (droits non interactifs)"
# Le script continuera avec sudo pour les commandes nécessitant root
else
echo "⚠️ Ce script nécessite les permissions root pour configurer Nginx"
echo " Utilisez: sudo $0"
exit 1
fi
fi
# Fonction pour exécuter les commandes nécessitant root
SUDO_CMD=""
if [ "$EUID" -ne 0 ]; then
SUDO_CMD="sudo"
fi
echo "✅ Vérification de Nginx..."
# Vérifier Nginx (peut être dans /usr/sbin/nginx)
NGINX_BIN=""
if command -v nginx &> /dev/null; then
NGINX_BIN="nginx"
elif [ -f /usr/sbin/nginx ]; then
NGINX_BIN="/usr/sbin/nginx"
elif [ -f /usr/bin/nginx ]; then
NGINX_BIN="/usr/bin/nginx"
else
echo "❌ Nginx n'est pas installé"
exit 1
fi
echo " Nginx trouvé: ${NGINX_BIN}"
echo "✅ Vérification de Certbot..."
# Vérifier Certbot (peut être dans /usr/bin/certbot)
CERTBOT_BIN=""
if command -v certbot &> /dev/null; then
CERTBOT_BIN="certbot"
elif [ -f /usr/bin/certbot ]; then
CERTBOT_BIN="/usr/bin/certbot"
else
echo "⚠️ Certbot n'est pas installé. Installation..."
${SUDO_CMD} apt-get update
${SUDO_CMD} apt-get install -y certbot python3-certbot-nginx
CERTBOT_BIN="certbot"
fi
echo " Certbot trouvé: ${CERTBOT_BIN}"
# Créer les configurations Nginx pour chaque sous-domaine
# 1. Dashboard (port 3020)
echo ""
echo "📝 Configuration de dashboard.certificator.4nkweb.com..."
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/dashboard.certificator.4nkweb.com" > /dev/null << 'EOF'
# Dashboard Bitcoin Signet
server {
listen 80;
server_name dashboard.certificator.4nkweb.com;
# Logs
access_log /var/log/nginx/dashboard.certificator.4nkweb.com.access.log;
error_log /var/log/nginx/dashboard.certificator.4nkweb.com.error.log;
# Proxy vers le service Node.js (port 3020)
# Note: Les services tournent sur 192.168.1.105
location / {
proxy_pass http://192.168.1.105:3020;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
}
EOF
# 2. Faucet (port 3021)
echo "📝 Configuration de faucet.certificator.4nkweb.com..."
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/faucet.certificator.4nkweb.com" > /dev/null << 'EOF'
# API Faucet Bitcoin Signet
server {
listen 80;
server_name faucet.certificator.4nkweb.com;
# Logs
access_log /var/log/nginx/faucet.certificator.4nkweb.com.access.log;
error_log /var/log/nginx/faucet.certificator.4nkweb.com.error.log;
# Proxy vers le service Node.js (port 3021)
# Note: Les services tournent sur 192.168.1.105
location / {
proxy_pass http://192.168.1.105:3021;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
}
EOF
# 3. Anchorage (port 3010)
echo "📝 Configuration de anchorage.certificator.4nkweb.com..."
${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/anchorage.certificator.4nkweb.com" > /dev/null << 'EOF'
# API Anchorage Bitcoin Signet
server {
listen 80;
server_name anchorage.certificator.4nkweb.com;
# Logs
access_log /var/log/nginx/anchorage.certificator.4nkweb.com.access.log;
error_log /var/log/nginx/anchorage.certificator.4nkweb.com.error.log;
# Proxy vers le service Node.js (port 3010)
# Note: Les services tournent sur 192.168.1.105
location / {
proxy_pass http://192.168.1.105:3010;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
}
EOF
# Activer les sites
echo ""
echo "🔗 Activation des sites..."
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/dashboard.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/dashboard.certificator.4nkweb.com"
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/faucet.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/faucet.certificator.4nkweb.com"
${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/anchorage.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/anchorage.certificator.4nkweb.com"
# Tester la configuration Nginx
echo ""
echo "🔍 Test de la configuration Nginx..."
if ${SUDO_CMD} ${NGINX_BIN} -t; then
echo "✅ Configuration Nginx valide"
else
echo "❌ Erreur dans la configuration Nginx"
exit 1
fi
# Recharger Nginx (configuration HTTP uniquement pour l'instant)
echo ""
echo "🔄 Rechargement de Nginx (configuration HTTP)..."
${SUDO_CMD} systemctl reload nginx || ${SUDO_CMD} service nginx reload
# Générer les certificats SSL avec Certbot
echo ""
echo "🔐 Génération des certificats SSL avec Certbot..."
echo " Note: Certbot va automatiquement créer les configurations HTTPS"
echo ""
# Générer les certificats (un par un pour éviter les erreurs)
DOMAINS=(
"dashboard.certificator.4nkweb.com"
"faucet.certificator.4nkweb.com"
"anchorage.certificator.4nkweb.com"
)
for domain in "${DOMAINS[@]}"; do
echo "📜 Génération du certificat pour ${domain}..."
# Certbot va automatiquement modifier la config pour ajouter HTTPS et redirection
if ${SUDO_CMD} ${CERTBOT_BIN} --nginx -d "${domain}" --non-interactive --agree-tos --email admin@4nkweb.com --redirect; then
echo "✅ Certificat généré et configuration HTTPS créée pour ${domain}"
else
echo "⚠️ Erreur lors de la génération du certificat pour ${domain}"
echo " Vous pouvez le générer manuellement avec:"
echo " sudo ${CERTBOT_BIN} --nginx -d ${domain}"
fi
done
# Recharger Nginx final
echo ""
echo "🔄 Rechargement final de Nginx..."
${SUDO_CMD} systemctl reload nginx || ${SUDO_CMD} service nginx reload
echo ""
echo "✅ Configuration terminée !"
echo ""
echo "📋 Résumé:"
echo " - dashboard.certificator.4nkweb.com -> http://192.168.1.105:3020"
echo " - faucet.certificator.4nkweb.com -> http://192.168.1.105:3021"
echo " - anchorage.certificator.4nkweb.com -> http://192.168.1.105:3010"
echo ""
echo "⚠️ Note: Si les services tournent sur une autre machine,"
echo " modifiez les IP dans les fichiers de configuration Nginx"
echo ""
echo "🔍 Vérification:"
echo " - Test Nginx: nginx -t"
echo " - Status: systemctl status nginx"
echo " - Logs: tail -f /var/log/nginx/*.error.log"
echo ""

View File

@ -0,0 +1,80 @@
# Vérification de Compatibilité - SIGNETCHALLENGE et PRIVKEY
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0
## Résumé
Vérification de la compatibilité entre le SIGNETCHALLENGE et la PRIVKEY configurés avant l'utilisation des descriptor wallets avec Bitcoin Core 30.2.
## Résultat
**COMPATIBLE** - Les clés sont compatibles et correctement configurées.
## Détails de la Vérification
### 1. Format SIGNETCHALLENGE
Le SIGNETCHALLENGE a le format : `5121[PUBKEY]51ae`
- **5121** : Préfixe pour script P2PK
- **[PUBKEY]** : Clé publique compressée (66 caractères hex)
- **51ae** : Suffixe OP_CHECKSIG
**Exemple** :
```
SIGNETCHALLENGE: 5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
Clé publique extraite: 028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6
```
### 2. Correspondance PRIVKEY → PUBKEY
La PRIVKEY doit pouvoir dériver la clé publique qui correspond à celle dans le SIGNETCHALLENGE.
**Vérification** :
```bash
PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2)
bitcoin-cli getdescriptorinfo "pk($PRIVKEY)" | jq -r '.descriptor'
# Doit contenir la même clé publique que dans SIGNETCHALLENGE
```
**Résultat** : ✅ La PRIVKEY dérive correctement vers la clé publique du SIGNETCHALLENGE.
### 3. Descriptor Wallet
Le descriptor `pk()` est importé dans le wallet avec :
- `hasprivatekeys: true`
- `issolvable: true`
- `descriptor: pk(028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6)#...`
## Conclusion
Les clés configurées avant l'utilisation des descriptor wallets sont **compatibles** avec Bitcoin Core 30.2 et les descriptor wallets.
**Pas besoin de redémarrer une nouvelle chaîne.**
Le problème de mining ("PSBT signing failed") vient d'ailleurs, probablement lié à :
- La façon dont Bitcoin Core 30+ gère les descriptors pour signer les transactions signet
- Un problème avec `walletprocesspsbt` et les PSBT spéciaux du signet
- Une limitation ou un bug dans Bitcoin Core 30.2 pour le mining signet avec descriptor wallets
## Commandes de Vérification
```bash
# Vérifier la correspondance
PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2)
SIGNETCHALLENGE=$(grep SIGNETCHALLENGE .env | cut -d'=' -f2)
PUBKEY_FROM_CHALLENGE="${SIGNETCHALLENGE:4:66}"
PUBKEY_FROM_PRIVKEY=$(bitcoin-cli getdescriptorinfo "pk($PRIVKEY)" | jq -r '.descriptor' | grep -o "02[0-9a-f]\{64\}")
if [ "$PUBKEY_FROM_CHALLENGE" = "$PUBKEY_FROM_PRIVKEY" ]; then
echo "✅ Compatible"
else
echo "❌ Incompatible"
fi
```
---
**Dernière mise à jour** : 2026-01-23

View File

@ -0,0 +1,595 @@
# Installation d'un Nouveau Nœud et Configuration du Mining
**Auteur** : Équipe 4NK
**Date** : 2026-01-09
**Version** : 1.0
Ce guide explique comment installer un nouveau nœud Bitcoin Signet custom et le configurer pour miner sur la chaîne existante.
## Table des Matières
1. [Prérequis](#prérequis)
2. [Installation Initiale](#installation-initiale)
3. [Configuration pour Rejoindre une Chaîne Existante](#configuration-pour-rejoindre-une-chaîne-existante)
4. [Configuration du Mining](#configuration-du-mining)
5. [Démarrage du Nœud](#démarrage-du-nœud)
6. [Vérification](#vérification)
7. [Dépannage](#dépannage)
---
## Prérequis
### Logiciels Requis
- **Docker** : Version 20.10 ou supérieure
- **Git** : Pour cloner le dépôt
- **jq** : Pour le traitement JSON (installé automatiquement dans le conteneur)
- **Accès réseau** : Ports 38332 (RPC), 38333 (P2P), 28332-28334 (ZMQ)
### Informations Nécessaires
Pour rejoindre une chaîne signet existante, vous devez obtenir du nœud administrateur :
1. **SIGNETCHALLENGE** : Le challenge du signet (identifie la chaîne)
2. **PRIVKEY** : La clé privée du signer (nécessaire pour miner)
3. **Adresse IP du nœud** : Pour se connecter au réseau (optionnel mais recommandé)
---
## Installation Initiale
### 1. Cloner le Dépôt
```bash
git clone https://github.com/Easepay/easepay-custom-signet.git bitcoin-signet
cd bitcoin-signet
```
### 2. Créer le Fichier de Configuration
Créez un fichier `.env` à la racine du projet :
```bash
cp env.example .env
```
### 3. Construire l'Image Docker
```bash
sudo docker build -t bitcoin-signet .
```
Cette étape peut prendre plusieurs minutes car elle télécharge Bitcoin Core 30.2.
---
## Configuration pour Rejoindre une Chaîne Existante
### Option A : Nœud Simple (Sans Mining)
Si vous voulez juste un nœud qui se synchronise avec la chaîne sans miner :
```bash
# Éditer .env
nano .env
```
Configuration minimale :
```bash
# Mining Configuration
BLOCKPRODUCTIONDELAY=600
MINERENABLED=0
NBITS=1e0377ae
PRIVKEY=
MINETO=
SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
# RPC Configuration
RPCUSER=bitcoin
RPCPASSWORD=bitcoin
UACOMMENT=CustomSignet
# ZMQ Configuration
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332
ZMQPUBRAWTX=tcp://0.0.0.0:28333
ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334
# Additional Configuration
RPCBIND=0.0.0.0:38332
RPCALLOWIP=0.0.0.0/0
WHITELIST=0.0.0.0/0
ADDNODE=<IP_DU_NOEUD_ADMINISTRATEUR>:38333
EXTERNAL_IP=
```
**Points importants** :
- `MINERENABLED=0` : Désactive le mining
- `SIGNETCHALLENGE` : Doit correspondre exactement à celui de la chaîne
- `ADDNODE` : Adresse IP et port du nœud administrateur (ex: `192.168.1.103:38333`)
### Option B : Nœud avec Mining
Si vous voulez miner sur la chaîne :
```bash
# Éditer .env
nano .env
```
Configuration complète :
```bash
# Mining Configuration
# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported)
BLOCKPRODUCTIONDELAY=600
MINERENABLED=1
NBITS=1e0377ae
PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS
MINETO=
SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
# RPC Configuration
RPCUSER=bitcoin
RPCPASSWORD=bitcoin
UACOMMENT=CustomSignet
# ZMQ Configuration
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332
ZMQPUBRAWTX=tcp://0.0.0.0:28333
ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334
# Additional Configuration
RPCBIND=0.0.0.0:38332
RPCALLOWIP=0.0.0.0/0
WHITELIST=0.0.0.0/0
ADDNODE=<IP_DU_NOEUD_ADMINISTRATEUR>:38333
EXTERNAL_IP=
```
**Points importants** :
- `MINERENABLED=1` : Active le mining
- `PRIVKEY` : Doit être la même que celle utilisée par le nœud administrateur
- `SIGNETCHALLENGE` : Doit correspondre exactement à celui de la chaîne
- `BLOCKPRODUCTIONDELAY` : Délai entre les blocs (600 = 10 minutes)
- `ADDNODE` : Adresse IP et port du nœud administrateur
---
## Configuration du Mining
### Paramètres de Mining
Les paramètres suivants contrôlent le comportement du mining :
#### BLOCKPRODUCTIONDELAY
Délai en secondes entre la génération de chaque bloc.
- **600** : 10 minutes (simule le mainnet)
- **60** : 1 minute (pour tests rapides)
- **0** : Pas de délai (mining continu)
**Exemple** :
```bash
BLOCKPRODUCTIONDELAY=600 # 10 minutes
```
#### NBITS
Difficulté minimale pour le mining (format hexadécimal).
**Recommandation** : Ne pas modifier sauf nécessité spécifique.
**Valeur par défaut** : `1e0377ae`
#### MINETO
Adresse Bitcoin où envoyer la récompense de minage.
- **Vide** : Une nouvelle adresse est générée pour chaque bloc
- **Adresse spécifique** : Tous les blocs minent vers cette adresse
**Exemple** :
```bash
MINETO=tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc
```
### Modification Dynamique du Délai
Vous pouvez modifier le délai de production de blocs sans redémarrer le conteneur :
```bash
# Modifier le délai à 60 secondes
sudo docker exec bitcoin-signet-instance bash -c "echo 60 > /root/.bitcoin/BLOCKPRODUCTIONDELAY.txt"
# Le script mine.sh détectera automatiquement ce changement
```
---
## Démarrage du Nœud
### Premier Démarrage
```bash
sudo docker run --env-file .env -d \
--name bitcoin-signet-instance \
-p 38332:38332 \
-p 38333:38333 \
-p 28332:28332 \
-p 28333:28333 \
-p 28334:28334 \
bitcoin-signet
```
### Vérifier les Logs
```bash
# Voir les logs en temps réel
sudo docker logs -f bitcoin-signet-instance
# Voir les derniers logs
sudo docker logs bitcoin-signet-instance --tail 50
```
### Arrêt du Nœud
```bash
# Arrêter le conteneur
sudo docker stop bitcoin-signet-instance
# Arrêter et supprimer le conteneur
sudo docker stop bitcoin-signet-instance && sudo docker rm bitcoin-signet-instance
```
### Redémarrage
```bash
# Redémarrer le conteneur
sudo docker restart bitcoin-signet-instance
# Ou recréer le conteneur
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
sudo docker run --env-file .env -d \
--name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
```
---
## Vérification
### 1. Vérifier que le Conteneur Fonctionne
```bash
# Vérifier le statut
sudo docker ps | grep bitcoin-signet
# Vérifier les logs
sudo docker logs bitcoin-signet-instance --tail 20
```
### 2. Vérifier la Connexion au Réseau
```bash
# Obtenir des informations sur le réseau
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo
# Vérifier les connexions
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep -E "(connections|networkactive)"
```
**Résultat attendu** :
- `networkactive: true`
- `connections: 1` ou plus (si connecté au nœud administrateur)
### 3. Vérifier la Synchronisation
```bash
# Obtenir des informations sur la blockchain
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
# Vérifier le dernier bloc
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash
```
**Résultat attendu** :
- `chain: "signet"`
- `verificationprogress: 1` (une fois synchronisé)
- `blocks: X` (nombre de blocs synchronisés)
### 4. Vérifier le Wallet (Si Mining Activé)
```bash
# Obtenir des informations sur le wallet
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo
# Vérifier que la clé privée est importée
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listdescriptors | grep -i "028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6"
```
**Résultat attendu** :
- `walletname: "custom_signet"`
- `format: "sqlite"` (descriptor wallet)
- Descriptor avec la clé publique du signet présent
### 5. Vérifier le Mining (Si Activé)
```bash
# Vérifier que le script de mining est actif
sudo docker exec bitcoin-signet-instance ps aux | grep -E "(mine|bitcoind)" | grep -v grep
# Voir les logs de mining
sudo docker logs bitcoin-signet-instance | grep -E "(Mine|Delay|block|Importing)"
```
**Résultat attendu** :
- Processus `mine.sh` en cours d'exécution
- Messages "Delay before next block" dans les logs
- Message "Private key imported successfully into descriptor wallet"
---
## Dépannage
### Le Nœud ne se Connecte pas au Réseau
**Symptômes** :
- `connections: 0`
- `networkactive: true` mais pas de connexions
**Solutions** :
1. **Vérifier ADDNODE dans .env** :
```bash
grep ADDNODE .env
```
Doit contenir l'adresse IP et le port du nœud administrateur : `ADDNODE=192.168.1.103:38333`
2. **Vérifier la connectivité réseau** :
```bash
# Tester la connexion au nœud administrateur
nc -zv <IP_NOEUD_ADMIN> 38333
```
3. **Vérifier le firewall** :
```bash
# Vérifier que le port 38333 est ouvert
sudo netstat -tlnp | grep 38333
```
4. **Vérifier SIGNETCHALLENGE** :
```bash
# Le SIGNETCHALLENGE doit être exactement le même que celui du nœud administrateur
grep SIGNETCHALLENGE .env
```
### Le Mining ne Fonctionne pas
**Symptômes** :
- Pas de processus `mine.sh`
- Erreurs dans les logs concernant la clé privée
**Solutions** :
1. **Vérifier MINERENABLED** :
```bash
grep MINERENABLED .env
```
Doit être `MINERENABLED=1`
2. **Vérifier PRIVKEY** :
```bash
grep PRIVKEY .env
```
Doit contenir la clé privée exacte du signet
3. **Vérifier l'import de la clé** :
```bash
sudo docker logs bitcoin-signet-instance | grep -i "importing\|private key"
```
Doit afficher "Private key imported successfully into descriptor wallet"
4. **Vérifier manuellement l'import** :
```bash
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listdescriptors | grep -i "wpkh"
```
Doit contenir le descriptor avec la clé publique du signet
### Erreur "Cannot obtain a lock"
**Symptômes** :
- Erreur dans les logs : "Cannot obtain a lock on data directory"
**Solutions** :
1. **Arrêter tous les processus bitcoind** :
```bash
sudo docker exec bitcoin-signet-instance pkill bitcoind
sudo docker restart bitcoin-signet-instance
```
2. **Vérifier qu'aucun autre conteneur n'utilise les mêmes ports** :
```bash
sudo docker ps | grep -E "(38332|38333)"
```
### Le Wallet n'Existe pas
**Symptômes** :
- Erreur "No wallet is loaded"
**Solutions** :
1. **Créer le wallet manuellement** :
```bash
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \
-named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=true
```
2. **Charger le wallet** :
```bash
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin loadwallet custom_signet
```
3. **Importer la clé privée manuellement** :
```bash
PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2)
DESCRIPTOR_INFO=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "wpkh($PRIVKEY)")
CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum')
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \
importdescriptors "[{\"desc\":\"wpkh($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]"
```
### Synchronisation Lente
**Symptômes** :
- `verificationprogress` reste faible
- `blocks` n'augmente pas
**Solutions** :
1. **Vérifier les connexions** :
```bash
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep connections
```
Doit être supérieur à 0
2. **Ajouter plusieurs nœuds dans ADDNODE** :
```bash
ADDNODE=192.168.1.103:38333,192.168.1.104:38333
```
3. **Vérifier la bande passante réseau**
---
## Commandes Utiles
### Accès au Conteneur
```bash
# Accéder au shell du conteneur
sudo docker exec -it bitcoin-signet-instance bash
# Exécuter bitcoin-cli
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin <commande>
```
### Commandes RPC Courantes
```bash
# État de la blockchain
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
# État du réseau
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo
# État du wallet
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo
# Obtenir une nouvelle adresse
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnewaddress
# Obtenir le solde
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbalance
# Liste des transactions
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listtransactions
```
### Monitoring
```bash
# Voir les logs en temps réel
sudo docker logs -f bitcoin-signet-instance
# Voir les logs de debug Bitcoin
sudo docker exec bitcoin-signet-instance tail -f /root/.bitcoin/signet/debug.log
# Vérifier les processus
sudo docker exec bitcoin-signet-instance ps aux | grep -E "(bitcoind|mine)"
```
---
## Partage d'Informations avec d'Autres Nœuds
Pour permettre à d'autres nœuds de rejoindre votre chaîne, partagez :
1. **SIGNETCHALLENGE** : Identifie la chaîne
```bash
sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/SIGNETCHALLENGE.txt
```
2. **PRIVKEY** : Nécessaire pour miner (partager uniquement avec les nœuds de confiance)
```bash
sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/PRIVKEY.txt
```
3. **Adresse IP et Port** : Pour la connexion P2P
- IP : Votre adresse IP publique ou locale
- Port : `38333` (port P2P)
4. **RPC Credentials** (optionnel) : Si vous voulez permettre l'accès RPC
- RPCUSER : `bitcoin` (ou personnalisé)
- RPCPASSWORD : `bitcoin` (ou personnalisé)
- Port RPC : `38332`
**Sécurité** : Ne partagez jamais la PRIVKEY publiquement. Elle permet de miner et de signer des blocs sur votre chaîne.
---
## Exemple de Configuration Complète
Voici un exemple complet de fichier `.env` pour un nœud avec mining :
```bash
# Mining Configuration
# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported)
BLOCKPRODUCTIONDELAY=600
MINERENABLED=1
NBITS=1e0377ae
PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS
MINETO=
SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
# RPC Configuration
RPCUSER=bitcoin
RPCPASSWORD=bitcoin
UACOMMENT=CustomSignet
# ZMQ Configuration
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332
ZMQPUBRAWTX=tcp://0.0.0.0:28333
ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334
# Additional Configuration
RPCBIND=0.0.0.0:38332
RPCALLOWIP=0.0.0.0/0
WHITELIST=0.0.0.0/0
ADDNODE=192.168.1.103:38333
EXTERNAL_IP=
```
---
## Prochaines Étapes
Une fois le nœud installé et fonctionnel :
1. **Lire la documentation de maintenance** : `docs/MAINTENANCE.md`
2. **Configurer la sauvegarde** : Voir section "Sauvegarde et Restauration"
3. **Configurer le monitoring** : Surveiller les logs et l'état du nœud
4. **Partager les informations** : Si vous voulez que d'autres nœuds rejoignent
---
**Dernière mise à jour** : 2026-01-09

461
docs/INTERFACES.md Normal file
View File

@ -0,0 +1,461 @@
# Interfaces et IHM Disponibles
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0
## Vue d'Ensemble
Ce document liste toutes les interfaces et IHM (Interfaces Homme-Machine) disponibles dans le projet Bitcoin Signet Custom.
---
## 1. API REST d'Ancrage
### Description
API REST HTTP/JSON pour ancrer des documents sur la blockchain Bitcoin Signet.
### Accès
- **URL** : `https://certificator.4nkweb.com` (via nginx proxy)
- **Port local** : `3010`
- **Protocole** : HTTPS (production) / HTTP (développement)
- **Format** : JSON
### Endpoints
#### GET `/`
Informations sur l'API
**Réponse** :
```json
{
"service": "bitcoin-signet-anchor-api",
"version": "1.0.0",
"endpoints": {
"health": "/health",
"anchor": "/api/anchor/document",
"verify": "/api/anchor/verify"
}
}
```
#### GET `/health`
Vérifie l'état de l'API et de la connexion Bitcoin
**Authentification** : Non requise
**Réponse** :
```json
{
"ok": true,
"service": "anchor-api",
"bitcoin": {
"connected": true,
"blocks": 152321
},
"timestamp": "2026-01-23T16:35:27.821Z"
}
```
#### POST `/api/anchor/document`
Ancre un document sur Bitcoin Signet
**Authentification** : Requise (header `x-api-key`)
**Body** :
```json
{
"documentUid": "doc-123456",
"hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}
```
**Réponse** :
```json
{
"txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85",
"status": "confirmed",
"confirmations": 0,
"block_height": 152321
}
```
#### POST `/api/anchor/verify`
Vérifie si un hash est ancré sur Bitcoin Signet
**Authentification** : Requise (header `x-api-key`)
**Body** :
```json
{
"hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890",
"txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85"
}
```
**Réponse** :
```json
{
"verified": true,
"anchor_info": {
"transaction_id": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85",
"block_height": 152321,
"confirmations": 0
}
}
```
### Documentation
- **Fichier** : `api-anchorage/README.md`
- **Client de test** : `api-anchorage/src/test-client.js`
### Exemple d'utilisation
```bash
# Health check
curl https://certificator.4nkweb.com/health
# Ancrer un document
curl -X POST https://certificator.4nkweb.com/api/anchor/document \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key" \
-d '{
"documentUid": "doc-123",
"hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}'
```
---
## 2. Interface RPC Bitcoin Core
### Description
Interface JSON-RPC pour contrôler et interagir avec le nœud Bitcoin Signet.
### Accès
- **Port** : `38332`
- **Protocole** : HTTP
- **Format** : JSON-RPC 2.0
- **Authentification** : Basic Auth (RPCUSER/RPCPASSWORD)
### Commandes Principales
#### Informations sur la Blockchain
```bash
# État de la blockchain
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
# Dernier bloc
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash
# Informations sur un bloc
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblock <hash>
```
#### Informations sur le Réseau
```bash
# État du réseau
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo
# Connexions actives
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getconnectioncount
```
#### Gestion du Wallet
```bash
# Informations sur le wallet
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo
# Solde
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbalance
# Nouvelle adresse
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnewaddress
# Transactions
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listtransactions
```
#### Accès depuis l'Extérieur
```bash
bitcoin-cli -rpcconnect=192.168.1.103 -rpcport=38332 \
-rpcuser=bitcoin -rpcpassword=bitcoin \
getblockchaininfo
```
### Documentation
- **Référence complète** : [Bitcoin Core RPC API](https://developer.bitcoin.org/reference/rpc/)
- **Documentation locale** : `docs/MAINTENANCE.md` (section "Accès RPC et API")
---
## 3. Interface Ligne de Commande (CLI)
### Description
Scripts shell et commandes pour interagir avec le système.
### Scripts Disponibles
#### `bitcoin-cli`
Interface en ligne de commande pour Bitcoin Core
**Utilisation** :
```bash
# Depuis le conteneur
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin <commande>
# Exemples
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo
```
#### `logtail.sh`
Visualisation des logs en temps réel
**Utilisation** :
```bash
./logtail.sh
```
#### `update-signet.sh`
Script de mise à jour du Bitcoin Signet
**Utilisation** :
```bash
./update-signet.sh [version]
./update-signet.sh --help
```
#### `mine.sh`
Script de mining (utilisé automatiquement par le conteneur)
**Utilisation** :
```bash
# Modifier le délai de production
sudo docker exec bitcoin-signet-instance bash -c "echo 60 > /root/.bitcoin/BLOCKPRODUCTIONDELAY.txt"
```
### Documentation
- **Scripts** : Voir les fichiers `.sh` à la racine du projet
- **Documentation** : `docs/MAINTENANCE.md`
---
## 4. Interface ZMQ (ZeroMQ)
### Description
Interface de messagerie pour recevoir des événements en temps réel du nœud Bitcoin.
### Accès
- **Ports** : `28332`, `28333`, `28334`
- **Protocole** : TCP
- **Format** : Messages binaires
### Topics Disponibles
#### `ZMQPUBRAWBLOCK` (port 28332)
Publication des blocs bruts
**Configuration** :
```bash
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332
```
#### `ZMQPUBRAWTX` (port 28333)
Publication des transactions brutes
**Configuration** :
```bash
ZMQPUBRAWTX=tcp://0.0.0.0:28333
```
#### `ZMQPUBHASHBLOCK` (port 28334)
Publication des hash de blocs
**Configuration** :
```bash
ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334
```
### Utilisation
**Exemple Python** :
```python
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://192.168.1.103:28332")
socket.setsockopt(zmq.SUBSCRIBE, b"rawblock")
while True:
topic, body = socket.recv_multipart()
print(f"Received block: {body.hex()}")
```
### Documentation
- **Référence** : [Bitcoin Core ZMQ](https://github.com/bitcoin/bitcoin/blob/master/doc/zmq.md)
---
## 5. Interface Docker
### Description
Commandes Docker pour gérer le conteneur Bitcoin Signet.
### Commandes Principales
#### Gestion du Conteneur
```bash
# Démarrer
sudo docker run --env-file .env -d \
--name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 \
-p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
# Arrêter
sudo docker stop bitcoin-signet-instance
# Redémarrer
sudo docker restart bitcoin-signet-instance
# Supprimer
sudo docker rm bitcoin-signet-instance
```
#### Logs
```bash
# Voir les logs
sudo docker logs bitcoin-signet-instance
# Suivre les logs en temps réel
sudo docker logs -f bitcoin-signet-instance
# Dernières lignes
sudo docker logs bitcoin-signet-instance --tail 50
```
#### Accès au Shell
```bash
# Accéder au shell du conteneur
sudo docker exec -it bitcoin-signet-instance bash
# Exécuter une commande
sudo docker exec bitcoin-signet-instance <commande>
```
### Documentation
- **Documentation** : `docs/MAINTENANCE.md` (section "Gestion du Conteneur")
---
## 6. Interface de Configuration (.env)
### Description
Fichier de configuration pour personnaliser le comportement du système.
### Accès
- **Fichier** : `.env` (à la racine du projet)
- **Format** : Variables d'environnement (KEY=VALUE)
### Variables Principales
#### Mining
```bash
BLOCKPRODUCTIONDELAY=600
MINERENABLED=1
NBITS=1e0377ae
PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS
MINETO=
SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
```
#### RPC
```bash
RPCUSER=bitcoin
RPCPASSWORD=bitcoin
RPCBIND=0.0.0.0:38332
RPCALLOWIP=0.0.0.0/0
```
#### ZMQ
```bash
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332
ZMQPUBRAWTX=tcp://0.0.0.0:28333
ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334
```
### Documentation
- **Exemple** : `env.example`
- **Documentation** : `docs/MAINTENANCE.md` (section "Configuration")
---
## 7. Interface de Logs
### Description
Système de logs pour surveiller l'activité du nœud.
### Accès
#### Logs Docker
```bash
# Logs du conteneur
sudo docker logs bitcoin-signet-instance
# Logs en temps réel
sudo docker logs -f bitcoin-signet-instance
```
#### Logs Bitcoin Core
```bash
# Logs de debug Bitcoin
sudo docker exec bitcoin-signet-instance tail -f /root/.bitcoin/signet/debug.log
```
#### Logs API
```bash
# Logs de l'API d'ancrage (si déployée avec PM2)
pm2 logs anchor-api
```
### Documentation
- **Script** : `logtail.sh`
- **Documentation** : `docs/MAINTENANCE.md` (section "Monitoring")
---
## Résumé des Ports
| Port | Protocole | Interface | Description |
|------|-----------|-----------|-------------|
| 3010 | HTTP/HTTPS | API REST | API d'ancrage (via nginx) |
| 38332 | HTTP | RPC Bitcoin | Interface JSON-RPC |
| 38333 | TCP | P2P Bitcoin | Réseau peer-to-peer |
| 28332 | TCP | ZMQ | Publication blocs bruts |
| 28333 | TCP | ZMQ | Publication transactions brutes |
| 28334 | TCP | ZMQ | Publication hash de blocs |
---
## Recommandations d'Utilisation
### Pour les Développeurs
- **API REST** : Pour intégrer l'ancrage dans des applications
- **RPC Bitcoin** : Pour des opérations avancées sur la blockchain
- **CLI** : Pour la maintenance et le dépannage
### Pour les Administrateurs
- **Docker CLI** : Pour gérer le conteneur
- **RPC Bitcoin** : Pour surveiller l'état du nœud
- **Logs** : Pour le dépannage et le monitoring
### Pour les Intégrations
- **API REST** : Interface principale pour les applications externes
- **ZMQ** : Pour recevoir des événements en temps réel
- **RPC Bitcoin** : Pour des opérations spécifiques non couvertes par l'API
---
**Dernière mise à jour** : 2026-01-23

814
docs/MAINTENANCE.md Normal file
View File

@ -0,0 +1,814 @@
# Documentation de Maintenance - Bitcoin Signet Custom
**Auteur** : Équipe 4NK
**Date** : 2026-01-09
**Version** : 1.0
## Table des Matières
1. [Vue d'Ensemble](#vue-densemble)
2. [Architecture](#architecture)
3. [Configuration](#configuration)
4. [Commandes de Maintenance](#commandes-de-maintenance)
5. [Gestion du Conteneur](#gestion-du-conteneur)
6. [Gestion des Clés et du Signet](#gestion-des-clés-et-du-signet)
7. [Mining](#mining)
8. [Accès RPC et API](#accès-rpc-et-api)
9. [Mise à Jour](#mise-à-jour)
10. [Dépannage](#dépannage)
11. [Modifications Apportées](#modifications-apportées)
12. [Sauvegarde et Restauration](#sauvegarde-et-restauration)
---
## Vue d'Ensemble
Ce projet installe et configure un Bitcoin Signet custom basé sur le dépôt [Easepay/easepay-custom-signet](https://github.com/Easepay/easepay-custom-signet.git).
Un Signet est un réseau de test Bitcoin qui permet de tester des applications Bitcoin sans risquer de fonds réels et sans l'imprévisibilité du testnet public.
### Caractéristiques
- **Version Bitcoin Core** : 30.2
- **Type de Wallet** : Descriptor wallets (legacy wallets non supportés depuis Bitcoin Core 30+)
- **Réseau** : Signet custom
- **Mining** : Activé avec délai configurable
- **Containerisation** : Docker
- **Base OS** : Debian Bookworm
---
## Architecture
### Structure des Fichiers
```
bitcoin/
├── Dockerfile # Image Docker avec Bitcoin Core
├── docker-entrypoint.sh # Point d'entrée du conteneur
├── install.sh # Script d'installation initiale
├── run.sh # Script de démarrage du nœud
├── setup-signet.sh # Configuration du signet
├── gen-signet-keys.sh # Génération des clés du signet
├── gen-bitcoind-conf.sh # Génération de bitcoin.conf
├── mine.sh # Script de mining
├── mine-genesis.sh # Mining du bloc genesis
├── logtail.sh # Visualisation des logs
├── rpcauth.py # Génération d'authentification RPC
├── miner # Script Python de mining
├── miner_imports/ # Imports pour le miner
├── .env # Variables d'environnement
├── env.example # Exemple de configuration
└── docs/ # Documentation
```
### Ports Exposés
| Port | Protocole | Service | Description |
|------|-----------|---------|-------------|
| 38332 | TCP | RPC | Interface JSON-RPC pour contrôler le nœud |
| 38333 | TCP/UDP | P2P | Réseau peer-to-peer Bitcoin Signet |
| 28332 | TCP | ZMQ | Publication des blocs bruts |
| 28333 | TCP | ZMQ | Publication des transactions brutes |
| 28334 | TCP | ZMQ | Publication des hash de blocs |
### Répertoires Importants dans le Conteneur
- `/root/.bitcoin/` : Répertoire de données Bitcoin
- `signet/` : Données de la chaîne signet
- `bitcoin.conf` : Configuration du nœud
- `PRIVKEY.txt` : Clé privée du signer
- `SIGNETCHALLENGE.txt` : Challenge du signet
- `MAGIC.txt` : Magic number du réseau
- `install_done` : Marqueur d'installation complète
---
## Configuration
### Fichier `.env`
Le fichier `.env` contient toutes les variables de configuration nécessaires au fonctionnement du signet.
#### Variables de Mining
```bash
# Délai entre la génération de chaque bloc (en secondes)
BLOCKPRODUCTIONDELAY=600
# Activer le mining (1 = activé, 0 = désactivé)
MINERENABLED=1
# Difficulté minimale pour le mining (format hexadécimal)
NBITS=1e0377ae
# Clé privée du signer (générée automatiquement si vide)
PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS
# Adresse de minage (générée automatiquement si vide)
MINETO=
# Challenge du signet (généré automatiquement si vide)
SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
```
#### Variables RPC
```bash
# Utilisateur RPC
RPCUSER=bitcoin
# Mot de passe RPC
RPCPASSWORD=bitcoin
```
#### Variables ZMQ
```bash
# Publication des blocs bruts
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332
# Publication des transactions brutes
ZMQPUBRAWTX=tcp://0.0.0.0:28333
# Publication des hash de blocs
ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334
```
#### Variables Réseau
```bash
# Commentaire User-Agent
UACOMMENT=CustomSignet
# Binding RPC
RPCBIND=0.0.0.0:38332
# IPs autorisées pour RPC
RPCALLOWIP=0.0.0.0/0
# IPs whitelistées
WHITELIST=0.0.0.0/0
# Nœuds à ajouter (séparés par des virgules)
ADDNODE=
# IP externe (pour l'annonce publique)
EXTERNAL_IP=
```
### Génération des Clés
Les clés sont générées automatiquement lors de la première installation si `PRIVKEY` et `SIGNETCHALLENGE` sont vides dans le `.env`.
**Important** : Une fois générées, ces clés doivent être conservées et partagées avec les autres nœuds qui souhaitent rejoindre le même signet.
---
## Commandes de Maintenance
### Vérification de l'État
```bash
# Vérifier que le conteneur est en cours d'exécution
sudo docker ps | grep bitcoin-signet
# Vérifier l'état de la blockchain
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
# Vérifier l'état du réseau
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo
# Vérifier l'état du wallet
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo
# Vérifier les processus en cours
sudo docker exec bitcoin-signet-instance ps aux | grep -E "(bitcoind|mine)"
```
### Logs
```bash
# Voir les logs en temps réel
sudo docker logs -f bitcoin-signet-instance
# Voir les derniers logs
sudo docker logs bitcoin-signet-instance --tail 50
# Voir les logs de debug Bitcoin
sudo docker exec bitcoin-signet-instance tail -f /root/.bitcoin/signet/debug.log
```
### Accès au Conteneur
```bash
# Accéder au shell du conteneur
sudo docker exec -it bitcoin-signet-instance bash
# Exécuter bitcoin-cli depuis l'hôte
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin <commande>
```
---
## Gestion du Conteneur
### Démarrage
```bash
cd /home/ncantu/Bureau/code/bitcoin
sudo docker run --env-file .env -d \
--name bitcoin-signet-instance \
-p 38332:38332 \
-p 38333:38333 \
-p 28332:28332 \
-p 28333:28333 \
-p 28334:28334 \
bitcoin-signet
```
### Arrêt
```bash
# Arrêter le conteneur
sudo docker stop bitcoin-signet-instance
# Arrêter et supprimer le conteneur
sudo docker stop bitcoin-signet-instance && sudo docker rm bitcoin-signet-instance
```
### Redémarrage
```bash
# Redémarrer le conteneur
sudo docker restart bitcoin-signet-instance
# Redémarrer après modification du .env
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
sudo docker run --env-file .env -d --name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
```
### Reconstruction de l'Image
```bash
cd /home/ncantu/Bureau/code/bitcoin
sudo docker build -t bitcoin-signet .
```
---
## Gestion des Clés et du Signet
### Récupération des Clés Générées
```bash
# Récupérer la clé privée
sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/PRIVKEY.txt
# Récupérer le challenge du signet
sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/SIGNETCHALLENGE.txt
# Récupérer le magic number
sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/MAGIC.txt
```
### Mise à Jour du `.env` avec les Clés
```bash
# Mettre à jour PRIVKEY dans .env
PRIVKEY=$(sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/PRIVKEY.txt)
sed -i "s/^PRIVKEY=.*/PRIVKEY=$PRIVKEY/" .env
# Mettre à jour SIGNETCHALLENGE dans .env
SIGNETCHALLENGE=$(sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/SIGNETCHALLENGE.txt)
sed -i "s/^SIGNETCHALLENGE=.*/SIGNETCHALLENGE=$SIGNETCHALLENGE/" .env
```
### Partage du Signet avec d'Autres Nœuds
Pour qu'un autre nœud rejoigne le même signet, il doit avoir dans son `.env` :
1. Le même `SIGNETCHALLENGE`
2. Le même `PRIVKEY` (si mining activé)
3. L'adresse IP du nœud dans `ADDNODE` (ex: `ADDNODE=192.168.1.100:38333`)
---
## Mining
### Configuration du Mining
Le mining est contrôlé par les variables suivantes dans `.env` :
- `MINERENABLED=1` : Active le mining
- `BLOCKPRODUCTIONDELAY=600` : Délai en secondes entre chaque bloc (600 = 10 minutes)
- `NBITS=1e0377ae` : Difficulté minimale (ne pas modifier sauf nécessité)
- `MINETO=` : Adresse de minage (vide = nouvelle adresse par bloc)
- `PRIVKEY` : Clé privée du signer (importée automatiquement dans le descriptor wallet)
**Note Bitcoin Core 30+** : La PRIVKEY est automatiquement importée dans le descriptor wallet au démarrage via `importdescriptors`. Cette importation est nécessaire car le miner utilise `walletprocesspsbt` pour signer les blocs, ce qui nécessite que la clé soit dans le wallet.
### Modification Dynamique du Délai
Pour modifier le délai de production de blocs sans redémarrer :
```bash
# Modifier le délai à 60 secondes (1 minute)
sudo docker exec bitcoin-signet-instance bash -c "echo 60 > /root/.bitcoin/BLOCKPRODUCTIONDELAY.txt"
```
Le script `mine.sh` lit automatiquement ce fichier s'il existe.
### Vérification du Mining
```bash
# Vérifier que le script de mining est actif
sudo docker exec bitcoin-signet-instance ps aux | grep mine.sh
# Voir les logs de mining
sudo docker logs bitcoin-signet-instance | grep -E "(Mine|Delay|block)"
# Vérifier le dernier bloc miné
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash
```
### Arrêt du Mining
Pour arrêter le mining, modifier `.env` :
```bash
MINERENABLED=0
```
Puis redémarrer le conteneur.
---
## Accès RPC et API
### Commandes RPC Courantes
```bash
# Obtenir des informations sur la blockchain
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
# Obtenir des informations sur le réseau
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo
# Obtenir des informations sur le wallet
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo
# Obtenir le solde
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbalance
# Obtenir une nouvelle adresse
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnewaddress
# Obtenir la liste des transactions
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listtransactions
# Obtenir le dernier bloc
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash
# Obtenir un bloc spécifique
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblock <hash>
```
### Accès RPC depuis l'Extérieur
Pour accéder au RPC depuis un autre hôte, utiliser :
```bash
bitcoin-cli -rpcconnect=<IP_DU_SERVEUR> -rpcport=38332 \
-rpcuser=bitcoin -rpcpassword=bitcoin \
getblockchaininfo
```
**Sécurité** : En production, restreindre `RPCALLOWIP` dans `.env` et utiliser une authentification plus forte.
### ZMQ
Les notifications ZMQ sont disponibles sur les ports 28332, 28333, 28334.
Exemple avec Python :
```python
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://localhost:28332")
socket.setsockopt(zmq.SUBSCRIBE, b"rawblock")
while True:
message = socket.recv()
print(f"New block: {message.hex()}")
```
---
## Mise à Jour
### Script de Mise à Jour Automatique
Un script de mise à jour automatique est disponible : `update-signet.sh`
#### Utilisation
```bash
# Mise à jour vers la dernière version disponible
./update-signet.sh
# Mise à jour vers une version spécifique
./update-signet.sh 30.2
# Aide
./update-signet.sh --help
```
#### Fonctionnalités du Script
Le script `update-signet.sh` effectue automatiquement :
1. **Vérification des prérequis** : Docker, fichier `.env`
2. **Détection de la version actuelle** : Depuis `.bitcoin-version` ou le Dockerfile
3. **Récupération de la dernière version** : Depuis les sources officielles Bitcoin
4. **Sauvegarde automatique** : Données du conteneur et fichier `.env`
5. **Mise à jour du Dockerfile** : Modification de la version Bitcoin Core
6. **Reconstruction de l'image** : Build de la nouvelle image Docker
7. **Redémarrage du conteneur** : Arrêt propre et redémarrage avec la nouvelle version
8. **Vérification post-mise à jour** : Contrôle de l'état du nœud
#### Procédure Manuelle de Mise à Jour
Si vous préférez effectuer la mise à jour manuellement :
##### 1. Vérifier la Version Actuelle
```bash
# Version dans le Dockerfile
grep BITCOIN_VERSION Dockerfile
# Version enregistrée
cat .bitcoin-version 2>/dev/null || echo "Non enregistrée"
# Version en cours d'exécution
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep subversion
```
##### 2. Récupérer la Dernière Version
Consultez les sources officielles :
- [Bitcoin Core Releases](https://bitcoincore.org/en/download/)
- [GitHub Releases](https://github.com/bitcoin/bitcoin/releases)
##### 3. Sauvegarder les Données
```bash
# Créer un répertoire de sauvegarde
mkdir -p backups
# Sauvegarder les données du conteneur
sudo docker exec bitcoin-signet-instance tar czf /tmp/bitcoin-backup.tar.gz /root/.bitcoin/
sudo docker cp bitcoin-signet-instance:/tmp/bitcoin-backup.tar.gz backups/signet-backup-$(date +%Y%m%d-%H%M%S).tar.gz
# Sauvegarder le .env
cp .env backups/.env.backup-$(date +%Y%m%d-%H%M%S)
```
##### 4. Mettre à Jour le Dockerfile
```bash
# Modifier la version dans le Dockerfile
sed -i 's/ARG BITCOIN_VERSION=\${BITCOIN_VERSION:-[0-9]\+\.[0-9]\+}/ARG BITCOIN_VERSION=${BITCOIN_VERSION:-30.2}/' Dockerfile
# Vérifier la modification
grep BITCOIN_VERSION Dockerfile
```
##### 5. Reconstruire l'Image
```bash
# Reconstruire l'image Docker
sudo docker build -t bitcoin-signet .
```
##### 6. Redémarrer le Conteneur
```bash
# Arrêter le conteneur actuel
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
# Démarrer avec la nouvelle image
sudo docker run --env-file .env -d \
--name bitcoin-signet-instance \
-p 38332:38332 \
-p 38333:38333 \
-p 28332:28332 \
-p 28333:28333 \
-p 28334:28334 \
bitcoin-signet
```
##### 7. Vérifier la Mise à Jour
```bash
# Attendre quelques secondes pour le démarrage
sleep 10
# Vérifier la version
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep subversion
# Vérifier l'état de la blockchain
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
# Vérifier les logs
sudo docker logs bitcoin-signet-instance --tail 50
```
##### 8. Enregistrer la Version
```bash
# Enregistrer la nouvelle version
echo "30.2" > .bitcoin-version
```
### Notes Importantes sur les Mises à Jour
#### Compatibilité des Versions
- **Bitcoin Core 26.0+** : Peut avoir des changements de compatibilité avec les wallets legacy
- **Bitcoin Core 30.2** : Version actuelle recommandée
- Vérifiez toujours les [release notes](https://bitcoincore.org/en/releases/) avant de mettre à jour
#### Impact sur le Mining
- Le mining reprend automatiquement après la mise à jour
- Les blocs minés avant la mise à jour restent valides
- Aucune perte de données si la sauvegarde est effectuée
#### Rollback en Cas de Problème
Si la mise à jour pose problème :
```bash
# Restaurer l'ancienne version du Dockerfile
git checkout HEAD -- Dockerfile
# Restaurer les données si nécessaire
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
sudo docker cp backups/signet-backup-YYYYMMDD-HHMMSS.tar.gz bitcoin-signet-instance:/tmp/
sudo docker exec bitcoin-signet-instance tar xzf /tmp/signet-backup-YYYYMMDD-HHMMSS.tar.gz -C /
# Reconstruire avec l'ancienne version
sudo docker build -t bitcoin-signet .
# Redémarrer
sudo docker run --env-file .env -d --name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
```
### Vérification Régulière des Mises à Jour
Pour vérifier régulièrement si une nouvelle version est disponible :
```bash
# Script de vérification
#!/bin/bash
CURRENT=$(cat .bitcoin-version 2>/dev/null || echo "unknown")
LATEST=$(curl -s https://bitcoincore.org/bin/ | grep -oP 'bitcoin-core-\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1)
if [ "$CURRENT" != "$LATEST" ]; then
echo "Nouvelle version disponible: $LATEST (actuelle: $CURRENT)"
echo "Exécutez: ./update-signet.sh $LATEST"
else
echo "Déjà à jour: $CURRENT"
fi
```
---
## Dépannage
### Problèmes Courants
#### Le conteneur ne démarre pas
```bash
# Vérifier les logs
sudo docker logs bitcoin-signet-instance
# Vérifier que les ports ne sont pas déjà utilisés
sudo netstat -tlnp | grep -E "(38332|38333|28332|28333|28334)"
# Vérifier que le fichier .env existe
ls -la .env
```
#### Bitcoind ne démarre pas
```bash
# Vérifier les logs de debug
sudo docker exec bitcoin-signet-instance tail -100 /root/.bitcoin/signet/debug.log
# Vérifier la configuration
sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/bitcoin.conf
# Vérifier les permissions
sudo docker exec bitcoin-signet-instance ls -la /root/.bitcoin/
```
#### Le mining ne fonctionne pas
```bash
# Vérifier que MINERENABLED=1 dans .env
grep MINERENABLED .env
# Vérifier que la clé privée est importée
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listprivkeys
# Vérifier que le script mine.sh est actif
sudo docker exec bitcoin-signet-instance ps aux | grep mine.sh
```
#### Erreur "Cannot obtain a lock"
Cette erreur indique qu'un autre processus bitcoind est déjà en cours d'exécution.
```bash
# Arrêter tous les processus bitcoind dans le conteneur
sudo docker exec bitcoin-signet-instance pkill bitcoind
# Redémarrer le conteneur
sudo docker restart bitcoin-signet-instance
```
#### Le wallet n'existe pas
```bash
# Créer le wallet manuellement (Bitcoin Core 30+ nécessite descriptor wallets)
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \
-named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=true
# Charger le wallet si nécessaire
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin loadwallet custom_signet
# Importer la clé privée si mining activé (Bitcoin Core 30+ nécessite descriptor wallets)
PRIVKEY=$(cat .env | grep PRIVKEY | cut -d'=' -f2)
DESCRIPTOR_INFO=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "wpkh($PRIVKEY)")
CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum')
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \
importdescriptors "[{\"desc\":\"wpkh($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]"
```
### Réinitialisation Complète
Si vous devez tout réinitialiser :
```bash
# Arrêter et supprimer le conteneur
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
# Supprimer le volume de données (ATTENTION : perte de données)
sudo docker volume rm $(sudo docker volume ls -q | grep bitcoin)
# Ou supprimer manuellement les données
sudo rm -rf /var/lib/docker/volumes/*/bitcoin-signet-instance/_data
# Relancer avec un nouveau .env (clés seront régénérées)
sudo docker run --env-file .env -d --name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
```
---
## Modifications Apportées
### Modifications du Dockerfile
1. **Mise à jour de Debian** : `buster-slim``bookworm-slim`
- Raison : Debian Buster n'est plus supporté
2. **Détection automatique de l'architecture** : Remplacement de `TARGETPLATFORM` par `uname -m`
- Raison : `TARGETPLATFORM` n'est pas défini par défaut dans `docker build`
3. **Installation de setuptools** : Via `apt` au lieu de `pip`
- Raison : PEP 668 empêche l'installation système via pip
### Modifications des Scripts
1. **gen-signet-keys.sh** :
- Correction de `ATADIR``DATADIR` (ligne 1)
- Mise à jour pour utiliser `descriptors=true` (Bitcoin Core 30+)
- Raison : Faute de frappe empêchait la création du répertoire temporaire, et Bitcoin Core 30+ nécessite des descriptor wallets
2. **setup-signet.sh** : Suppression du démarrage de bitcoind
- Raison : Éviter les conflits de verrou, bitcoind est démarré par `run.sh`
3. **run.sh** : Ajout de la création du wallet descriptor et import automatique de la clé privée
- Raison : Le wallet doit être créé après le démarrage de bitcoind
- Bitcoin Core 30+ nécessite des descriptor wallets (legacy wallets non supportés)
- La clé privée est importée automatiquement dans le descriptor wallet pour permettre le mining
- L'import utilise `importdescriptors` avec le checksum requis (obtenu via `getdescriptorinfo`)
- Le miner utilise `walletprocesspsbt` qui nécessite que la clé soit dans le wallet
---
## Sauvegarde et Restauration
### Sauvegarde
```bash
# Sauvegarder les données Bitcoin
sudo docker exec bitcoin-signet-instance tar czf /tmp/bitcoin-backup.tar.gz /root/.bitcoin/
# Copier la sauvegarde hors du conteneur
sudo docker cp bitcoin-signet-instance:/tmp/bitcoin-backup.tar.gz ./bitcoin-backup-$(date +%Y%m%d).tar.gz
# Sauvegarder le fichier .env
cp .env .env.backup-$(date +%Y%m%d)
```
### Restauration
```bash
# Arrêter le conteneur
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
# Créer un nouveau conteneur
sudo docker run --env-file .env -d --name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
# Attendre que le conteneur démarre
sleep 5
# Restaurer les données
sudo docker cp bitcoin-backup-YYYYMMDD.tar.gz bitcoin-signet-instance:/tmp/
sudo docker exec bitcoin-signet-instance tar xzf /tmp/bitcoin-backup-YYYYMMDD.tar.gz -C /
# Redémarrer le conteneur
sudo docker restart bitcoin-signet-instance
```
### Sauvegarde des Clés
**Important** : Toujours sauvegarder séparément :
- Le fichier `.env` (contient `PRIVKEY` et `SIGNETCHALLENGE`)
- Les fichiers `/root/.bitcoin/PRIVKEY.txt` et `/root/.bitcoin/SIGNETCHALLENGE.txt`
Ces clés sont essentielles pour maintenir la cohérence du signet.
---
## Commandes Utiles
### Script de Vérification Rapide
```bash
#!/bin/bash
echo "=== État du Conteneur ==="
sudo docker ps | grep bitcoin-signet
echo -e "\n=== État de la Blockchain ==="
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo | grep -E "(chain|blocks|bestblockhash)"
echo -e "\n=== État du Réseau ==="
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep -E "(networkactive|connections)"
echo -e "\n=== État du Wallet ==="
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo | grep -E "(walletname|balance)"
echo -e "\n=== Processus de Mining ==="
sudo docker exec bitcoin-signet-instance ps aux | grep -E "(bitcoind|mine)" | grep -v grep
```
### Script de Monitoring
```bash
#!/bin/bash
watch -n 5 'sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo | grep -E "(chain|blocks|bestblockhash|difficulty)"'
```
---
## Références
- [Dépôt Source](https://github.com/Easepay/easepay-custom-signet.git)
- [Documentation Bitcoin Signet](https://en.bitcoin.it/wiki/Signet)
- [Bitcoin Core RPC API](https://developer.bitcoin.org/reference/rpc/)
- [Documentation Docker](https://docs.docker.com/)
---
**Dernière mise à jour** : 2026-01-09

152
docs/PROMPT_IA_RECHERCHE.md Normal file
View File

@ -0,0 +1,152 @@
# Prompt pour Recherche IA - Problème Mining Signet Bitcoin Core 30.2
**Contexte** : Problème de mining Bitcoin Signet avec Bitcoin Core 30.2 et descriptor wallets
---
## PROMPT COMPLET
Je rencontre un problème avec le mining Bitcoin Signet sur Bitcoin Core 30.2 utilisant des descriptor wallets. Le miner échoue systématiquement avec "PSBT signing failed" malgré une configuration apparemment correcte.
### Contexte Technique
**Environnement** :
- Bitcoin Core 30.2 (dernière version)
- Descriptor wallets (legacy wallets non supportés depuis Bitcoin Core 30+)
- Bitcoin Signet custom
- Docker container (Debian Bookworm)
- Script de mining Python basé sur `contrib/signet/miner.py` de Bitcoin Core
### Configuration Actuelle
**SIGNETCHALLENGE** : `5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae`
- Format : `5121[PUBKEY]51ae` (script P2PK)
- Clé publique : `028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6`
**PRIVKEY** : `cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS`
- Format WIF (Wallet Import Format)
- Dérive correctement vers la clé publique du SIGNETCHALLENGE ✅
**Wallet Descriptor** :
```json
{
"desc": "pk(028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6)#vflt9jvv",
"timestamp": 1,
"active": false,
"has_private_keys": true,
"is_solvable": true
}
```
### Problème Observé
Le miner Bitcoin Signet utilise `walletprocesspsbt` pour signer des transactions spéciales vers le SIGNETCHALLENGE. Le processus échoue à chaque tentative :
**Logs** :
```
PSBT signing failed
Delay before next block 600 seconds.
```
**Code du miner** (ligne 476-482) :
```python
psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time)
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
if not psbt_signed.get("complete",False):
logging.debug("Generated PSBT: %s" % (psbt,))
sys.stderr.write("PSBT signing failed\n")
return 1
```
### Structure du PSBT Signet
Le miner crée un PSBT spécial qui contient :
1. Une transaction `to_spend` vers le SIGNETCHALLENGE (script P2PK)
2. Une transaction `spend` qui dépense `to_spend`
3. Le bloc à miner dans un champ propriétaire PSBT (`PSBT_SIGNET_BLOCK`)
**Code de génération** (ligne 134-166) :
```python
def signet_txs(block, challenge):
# ... crée to_spend et spend transactions
to_spend.vout = [CTxOut(0, challenge)] # challenge est le SIGNETCHALLENGE (P2PK script)
return spend, to_spend
```
### Vérifications Effectuées
**Compatibilité des clés** :
- La PRIVKEY dérive correctement vers la clé publique du SIGNETCHALLENGE
- Le descriptor `pk()` est importé dans le wallet
- `hasprivatekeys: true` et `issolvable: true`
**Import du descriptor** :
- Utilisation de `importdescriptors` avec `pk($PRIVKEY)#$CHECKSUM`
- Le descriptor est présent dans le wallet (mais `active: false`)
**Problème** :
- `walletprocesspsbt` retourne `complete: false`
- Le wallet ne peut pas signer le PSBT signet
### Tentatives de Résolution
1. **Import comme `pk()` au lieu de `wpkh()`**
- Nécessaire car SIGNETCHALLENGE utilise P2PK, pas P2WPKH
- Le descriptor est importé avec succès
2. **Tentative d'activation du descriptor**
- Erreur : "Active descriptors must be ranged"
- Les descriptors non-rangés ne peuvent pas être actifs
3. **Vérification de compatibilité**
- Les clés sont compatibles
- Pas besoin de régénérer la chaîne
### Questions Spécifiques
1. **Bitcoin Core 30.2 et descriptor wallets** :
- Y a-t-il des limitations connues pour signer des transactions P2PK avec des descriptors `pk()` non-actifs ?
- Le wallet peut-il signer des transactions vers des scripts qui ne sont pas dans son UTXO set ?
2. **PSBT Signet et walletprocesspsbt** :
- `walletprocesspsbt` peut-il signer des PSBT spéciaux contenant des transactions virtuelles (comme `to_spend` dans signet) ?
- Y a-t-il des paramètres spécifiques nécessaires pour signer des PSBT signet ?
3. **Descriptor `pk()` non-actif** :
- Un descriptor `pk()` avec `active: false` peut-il être utilisé pour signer ?
- Faut-il importer la clé différemment pour le mining signet ?
4. **Alternatives** :
- Existe-t-il une autre méthode pour signer les blocs signet avec Bitcoin Core 30.2 ?
- Faut-il utiliser une approche différente pour les descriptor wallets ?
### Informations Supplémentaires
**Version Bitcoin Core** : 30.2.0
**Type de wallet** : Descriptor wallet (sqlite)
**Méthode d'import** : `importdescriptors` avec descriptor `pk($PRIVKEY)#$CHECKSUM`
**Script de mining** : Basé sur `contrib/signet/miner.py` de Bitcoin Core
**Commandes de test** :
```bash
# Vérifier le descriptor
bitcoin-cli listdescriptors | grep "pk("
# Tester walletprocesspsbt (échoue)
miner --cli="bitcoin-cli" generate --nbits=1e0377ae --set-block-time=$(date +%s)
```
### Résultat Attendu
Le wallet devrait pouvoir signer le PSBT signet via `walletprocesspsbt`, permettant au miner de créer et soumettre des blocs sur le signet.
### Ressources Consultées
- Documentation Bitcoin Core RPC : `walletprocesspsbt`, `importdescriptors`
- Code source : `contrib/signet/miner.py`
- Documentation descriptor wallets Bitcoin Core 30+
---
**Question principale** : Comment faire fonctionner le mining Bitcoin Signet avec Bitcoin Core 30.2 et descriptor wallets lorsque `walletprocesspsbt` échoue à signer le PSBT signet malgré un descriptor `pk()` correctement importé avec les clés privées ?

65
docs/README.md Normal file
View File

@ -0,0 +1,65 @@
# Documentation Bitcoin Signet Custom
Ce dossier contient toute la documentation nécessaire pour la maintenance et l'utilisation du Bitcoin Signet custom.
## Fichiers de Documentation
- **[MAINTENANCE.md](./MAINTENANCE.md)** : Documentation complète de maintenance
- Architecture et structure
- Configuration et variables d'environnement
- Commandes de maintenance
- Gestion du conteneur
- Mining
- Accès RPC et API
- Mise à jour
- Dépannage
- Sauvegarde et restauration
- **[INSTALLATION_NEW_NODE.md](./INSTALLATION_NEW_NODE.md)** : Guide d'installation d'un nouveau nœud
- Installation initiale
- Configuration pour rejoindre une chaîne existante
- Configuration du mining
- Vérification et dépannage
- Partage d'informations avec d'autres nœuds
- **[INTERFACES.md](./INTERFACES.md)** : Documentation des interfaces disponibles
- API REST d'ancrage
- Interface RPC Bitcoin Core
- Interface ligne de commande (CLI)
- Interface ZMQ (ZeroMQ)
- Interface Docker
- Interface de configuration
- Interface de logs
## Démarrage Rapide
### Installation
```bash
cd /home/ncantu/Bureau/code/bitcoin
sudo docker build -t bitcoin-signet .
sudo docker run --env-file .env -d --name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
```
### Vérification
```bash
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo
```
### Logs
```bash
sudo docker logs -f bitcoin-signet-instance
```
## Informations Importantes
- **Ports** : RPC (38332), P2P (38333), ZMQ (28332-28334)
- **Configuration** : Fichier `.env` à la racine du projet
- **Données** : Stockées dans `/root/.bitcoin/` dans le conteneur
- **Clés** : Générées automatiquement, stockées dans `.env` et `/root/.bitcoin/PRIVKEY.txt`
Pour plus de détails, consultez [MAINTENANCE.md](./MAINTENANCE.md).

104
docs/SOLUTION_MINING.md Normal file
View File

@ -0,0 +1,104 @@
# Solution au Problème de Mining Signet - Bitcoin Core 30.2
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0
## Problème Résolu
Le mining Bitcoin Signet échouait avec "PSBT signing failed" sur Bitcoin Core 30.2 avec descriptor wallets.
## Cause Racine
Le problème venait de l'utilisation de `walletprocesspsbt` qui ne peut pas signer les PSBT signet contenant des transactions artificielles (`to_spend`/`spend`). Ces transactions ne sont pas des UTXOs réels dans le wallet, donc `walletprocesspsbt` ne peut pas les reconnaître et signer.
## Solution
**Remplacer `walletprocesspsbt` par `descriptorprocesspsbt`**
`descriptorprocesspsbt` est un RPC de nœud (pas wallet) qui signe directement avec des descriptors fournis, sans dépendre du wallet et de son modèle UTXO.
### Modifications Apportées
#### 1. Fichier `miner` (ligne ~476-521)
**Avant** :
```python
psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
```
**Après** :
```python
# Use descriptorprocesspsbt by default (works with Bitcoin Core 30+ and descriptor wallets)
privkey = os.environ.get('PRIVKEY', '')
if not privkey and hasattr(args, 'privkey') and args.privkey:
privkey = args.privkey
if privkey:
descriptor = "pk(%s)" % privkey
descriptors_array = [descriptor]
psbt_signed = json.loads(args.bcli("descriptorprocesspsbt", psbt, json.dumps(descriptors_array), "ALL", "true", "true"))
else:
# Fallback to walletprocesspsbt
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
```
#### 2. Fichier `mine.sh`
**Modification** : Export de PRIVKEY pour qu'il soit disponible dans l'environnement du miner
```bash
export PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt 2>/dev/null || echo "")}
miner --cli="bitcoin-cli" generate ...
```
### Fonctionnement
1. Le miner génère un PSBT signet avec les transactions artificielles `to_spend`/`spend`
2. Au lieu d'utiliser `walletprocesspsbt`, il utilise `descriptorprocesspsbt`
3. `descriptorprocesspsbt` reçoit :
- Le PSBT (base64)
- Un array de descriptors : `["pk(PRIVKEY)"]`
- Les paramètres : `"ALL"`, `true`, `true` (sighashtype, bip32derivs, finalize)
4. Le PSBT est signé avec succès (`complete: true`)
5. Le bloc est miné et soumis
### Avantages
- ✅ Fonctionne avec Bitcoin Core 30+ et descriptor wallets
- ✅ Ne dépend pas du wallet pour reconnaître les UTXOs
- ✅ Signe directement avec le descriptor contenant la clé privée
- ✅ Compatible avec les transactions artificielles Signet
### Vérification
**Test manuel** :
```bash
PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2)
PSBT=$(miner genpsbt ...)
bitcoin-cli -signet descriptorprocesspsbt "$PSBT" '["pk('$PRIVKEY')"]' "ALL" true true
# Résultat: {"psbt": "...", "complete": true, ...}
```
**Test automatique** :
```bash
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo | grep blocks
# Doit afficher blocks > 0 après quelques minutes
```
### Notes Techniques
- `descriptorprocesspsbt` est un RPC de nœud, pas de wallet
- Il accepte les descriptors avec clés privées WIF
- Il peut signer des PSBT avec des entrées qui ne sont pas dans le wallet
- Parfait pour les cas d'usage comme Signet où les transactions sont artificielles
### Références
- [Bitcoin Core descriptorprocesspsbt](https://bitcoincore.org/en/doc/28.0.0/rpc/rawtransactions/descriptorprocesspsbt/)
- [Bitcoin Core PSBT Documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md)
- [GitHub Issue #28911](https://github.com/bitcoin/bitcoin/issues/28911)
---
**Dernière mise à jour** : 2026-01-23

View File

@ -0,0 +1,94 @@
# Dépannage du Mining - Bitcoin Signet
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0
## Problème : Aucun bloc n'est miné - "PSBT signing failed"
### Symptômes
- Les logs affichent : `PSBT signing failed`
- Aucun bloc n'est miné malgré `MINERENABLED=1`
- Le processus `mine.sh` est actif mais échoue à chaque tentative
### Cause
Le miner Bitcoin Signet utilise `walletprocesspsbt` pour signer des transactions spéciales vers le SIGNETCHALLENGE. Ces transactions utilisent un script P2PK (Pay-to-Public-Key), pas P2WPKH (Pay-to-Witness-Public-Key-Hash).
**Problème** : Si la clé privée est importée uniquement comme `wpkh()` (P2WPKH), le wallet ne peut pas signer les transactions P2PK nécessaires pour le signet.
### Solution
La clé privée doit être importée comme `pk()` (P2PK) dans le wallet descriptor pour permettre la signature des transactions signet.
**Fichier modifié** : `run.sh`
**Avant** (incorrect) :
```bash
DESCRIPTOR_INFO=$(bitcoin-cli -datadir=$DATADIR getdescriptorinfo "wpkh($PRIVKEY)")
IMPORT_RESULT=$(bitcoin-cli -datadir=$DATADIR importdescriptors "[{\"desc\":\"wpkh($PRIVKEY)#$CHECKSUM\",...}]")
```
**Après** (correct) :
```bash
DESCRIPTOR_INFO=$(bitcoin-cli -datadir=$DATADIR getdescriptorinfo "pk($PRIVKEY)")
IMPORT_RESULT=$(bitcoin-cli -datadir=$DATADIR importdescriptors "[{\"desc\":\"pk($PRIVKEY)#$CHECKSUM\",...}]")
```
### Vérification
1. **Vérifier que le descriptor pk() est importé** :
```bash
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listdescriptors | grep "pk("
```
2. **Vérifier que la clé correspond au SIGNETCHALLENGE** :
```bash
PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2)
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "pk($PRIVKEY)" | jq -r '.descriptor'
# Doit contenir la clé publique du SIGNETCHALLENGE
```
3. **Vérifier les logs** :
```bash
sudo docker logs bitcoin-signet-instance | grep -E "(PSBT|signing|Mine)"
```
### Correction Manuelle (si nécessaire)
Si le problème persiste après la modification de `run.sh`, importer manuellement :
```bash
PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2)
DESCRIPTOR_INFO=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "pk($PRIVKEY)")
CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum')
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \
importdescriptors "[{\"desc\":\"pk($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]"
```
### Redémarrage
Après modification de `run.sh`, reconstruire l'image et redémarrer :
```bash
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
sudo docker build -t bitcoin-signet .
sudo docker run --env-file .env -d \
--name bitcoin-signet-instance \
-p 38332:38332 -p 38333:38333 \
-p 28332:28332 -p 28333:28333 -p 28334:28334 \
bitcoin-signet
```
### Notes Techniques
- **SIGNETCHALLENGE** : Contient un script P2PK avec la clé publique du signet
- **Miner** : Crée une transaction `to_spend` vers ce script P2PK qui doit être signée
- **walletprocesspsbt** : Nécessite que le wallet ait la clé privée correspondante au script P2PK
- **Descriptor pk()** : Permet au wallet de signer pour les scripts P2PK
---
**Dernière mise à jour** : 2026-01-23

View File

@ -1,8 +1,10 @@
# Mining Configuration
# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported)
BLOCKPRODUCTIONDELAY=<Specify delay in seconds>
MINERENABLED=1
NBITS=<Your minimum difficulty value>
PRIVKEY=<Your private key for Signet, if available>
# Note: PRIVKEY is automatically imported into descriptor wallet at startup for mining
MINETO=<Your mining address, if available>
SIGNETCHALLENGE=<Your Signet challenge>

View File

@ -1,4 +1,4 @@
ATADIR=${DATADIR:-"regtest-temp"}
DATADIR=${DATADIR:-"regtest-temp"}
BITCOINCLI=${BITCOINCLI:-"bitcoin-cli -regtest -datadir=$DATADIR "}
BITCOIND=${BITCOIND:-"bitcoind -datadir=$DATADIR -regtest -daemon "}
@ -33,9 +33,8 @@ if [[ "$MINERENABLED" == "1" && ("$SIGNETCHALLENGE" == "" || "$PRIVKEY" == "") ]
$BITCOIND -wallet="temp"
#wait a bit for startup
sleep 5s
#create wallet
# todo, redo to work with descriptors
$BITCOINCLI -named createwallet wallet_name="tmp" descriptors=false
#create wallet (Bitcoin Core 30+ requires descriptor wallets)
$BITCOINCLI -named createwallet wallet_name="tmp" descriptors=true
#export future signet seeding key data
ADDR=$($BITCOINCLI getnewaddress)
PRIVKEY=$($BITCOINCLI dumpprivkey $ADDR)

1
mempool Submodule

@ -0,0 +1 @@
Subproject commit 912f74bea073b2857b49d850cdd53be748188ccf

View File

@ -15,5 +15,9 @@ while true; do
fi
fi
echo "Mine To:" $ADDR
# PRIVKEY should be available from .env via run.sh environment
# The miner will automatically use PRIVKEY from environment for descriptorprocesspsbt
# Export PRIVKEY to ensure it's available to the miner process
export PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt 2>/dev/null || echo "")}
miner --cli="bitcoin-cli" generate --grind-cmd="bitcoin-util grind" --address=$ADDR --nbits=$NBITS --set-block-time=$(date +%s)
done

53
miner
View File

@ -474,8 +474,53 @@ def do_generate(args):
logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine)
mined_blocks += 1
psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time)
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
# Use descriptorprocesspsbt instead of walletprocesspsbt for Bitcoin Core 30+
# descriptorprocesspsbt can sign PSBT with artificial UTXOs (to_spend/spend) used in Signet
# It doesn't require the wallet to recognize the UTXO, just the descriptor with private key
if hasattr(args, 'signer') and args.signer == 'descriptor':
# Get private key from descriptor or use provided one
privkey = args.descriptor if args.descriptor and not '*' in args.descriptor else None
if privkey is None and hasattr(args, 'privkey') and args.privkey:
privkey = args.privkey
if privkey:
# Extract WIF from descriptor if needed (pk(WIF) or wpkh(WIF))
if privkey.startswith('pk(') or privkey.startswith('wpkh('):
privkey = privkey.split('(')[1].split(')')[0].split('#')[0]
descriptor = "pk(%s)" % privkey
# Use descriptorprocesspsbt: PSBT, descriptors array (JSON), sighashtype, bip32derivs, finalize
descriptors_array = [descriptor]
psbt_signed = json.loads(args.bcli("descriptorprocesspsbt", psbt, json.dumps(descriptors_array), "ALL", "true", "true"))
else:
# Fallback to walletprocesspsbt if no descriptor/privkey provided
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
else:
# Use descriptorprocesspsbt by default (works with Bitcoin Core 30+ and descriptor wallets)
# This avoids the wallet UTXO recognition issue with artificial Signet transactions
try:
# Get privkey from environment or args
privkey = os.environ.get('PRIVKEY', '')
if not privkey and hasattr(args, 'privkey') and args.privkey:
privkey = args.privkey
if privkey:
descriptor = "pk(%s)" % privkey
# Use descriptorprocesspsbt: PSBT, descriptors array (JSON), sighashtype, bip32derivs, finalize
# bitcoin_cli adds -signet automatically and converts args to strings
descriptors_array = [descriptor]
psbt_signed = json.loads(args.bcli("descriptorprocesspsbt", psbt, json.dumps(descriptors_array), "ALL", "true", "true"))
logging.debug("Used descriptorprocesspsbt for signing")
else:
# Fallback to walletprocesspsbt if no PRIVKEY available
logging.debug("No PRIVKEY found, falling back to walletprocesspsbt")
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
except Exception as e:
# If descriptorprocesspsbt fails, try walletprocesspsbt as fallback
logging.debug("descriptorprocesspsbt failed, trying walletprocesspsbt: %s" % str(e))
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
if not psbt_signed.get("complete",False):
logging.debug("Generated PSBT: %s" % (psbt,))
sys.stderr.write("PSBT signing failed\n")
@ -584,6 +629,10 @@ def main():
args.bcli = lambda *a, input=b"", **kwargs: bitcoin_cli(args.cli.split(" "), list(a), input=input, **kwargs)
# Get PRIVKEY from environment or argument for descriptor signing
if not hasattr(args, 'privkey') or args.privkey is None:
args.privkey = os.environ.get('PRIVKEY', '')
if hasattr(args, "address") and hasattr(args, "descriptor"):
if args.address is None and args.descriptor is None:
sys.stderr.write("Must specify --address or --descriptor\n")

38
run.sh
View File

@ -3,6 +3,44 @@
# run bitcoind
bitcoind --daemonwait
sleep 5
# Create wallet if it doesn't exist
DATADIR=${DATADIR:-~/.bitcoin/}
if ! bitcoin-cli -datadir=$DATADIR listwallets | grep -q "custom_signet"; then
echo "Creating wallet custom_signet"
bitcoin-cli -datadir=$DATADIR -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=true
fi
# Load wallet if not already loaded
if ! bitcoin-cli -datadir=$DATADIR listwallets | grep -q "custom_signet"; then
bitcoin-cli -datadir=$DATADIR loadwallet custom_signet
fi
# Import private key if in mining mode (Bitcoin Core 30+ requires descriptor wallets)
# The miner needs the private key in the wallet to sign blocks via walletprocesspsbt
# IMPORTANT: For signet mining, we need to import as pk() (P2PK) not wpkh() (P2WPKH)
# because the SIGNETCHALLENGE uses a P2PK script that must be signed
if [[ "$MINERENABLED" == "1" ]]; then
PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt)}
if [[ -n "$PRIVKEY" ]]; then
echo "Importing private key for mining (descriptor wallet)"
# Import as pk() for P2PK signing (required for signet challenge)
# The signet miner needs to sign transactions to the SIGNETCHALLENGE which is a P2PK script
# Note: pk() descriptors cannot be active (must be ranged), but they can still be used for signing
DESCRIPTOR_INFO=$(bitcoin-cli -datadir=$DATADIR getdescriptorinfo "pk($PRIVKEY)")
CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum')
# Import descriptor with checksum (required for Bitcoin Core 30+)
# The descriptor doesn't need to be active to sign - it just needs to be in the wallet
IMPORT_RESULT=$(bitcoin-cli -datadir=$DATADIR importdescriptors "[{\"desc\":\"pk($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]")
if echo "$IMPORT_RESULT" | jq -e '.[0].success == true' > /dev/null; then
echo "Private key imported successfully into descriptor wallet (P2PK for signet)"
else
echo "Warning: Private key import may have failed"
echo "Import result: $IMPORT_RESULT"
fi
fi
fi
echo "get magic"
magic=$(cat /root/.bitcoin/signet/debug.log | grep -m1 magic)
magic=${magic:(-8)}

View File

@ -1,17 +1,4 @@
PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt)}
DATADIR=${DATADIR:-~/.bitcoin/}
bitcoind -datadir=$DATADIR --daemonwait -persistmempool
bitcoin-cli -datadir=$DATADIR -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=false
# bitcoin-cli -datadir=$DATADIR loadwallet "custom_signet"
#only used in case of mining node
if [[ "$MINERENABLED" == "1" ]]; then
bitcoin-cli -datadir=$DATADIR importprivkey $PRIVKEY
## for future with descriptor wallets, cannot seem to get it working yet
# descinfo=$(bitcoin-cli getdescriptorinfo "wpkh(${PRIVKEY})")
# checksum=$(echo "$descinfo" | jq .checksum | tr -d '"' | tr -d "\n")
# desc='[{"desc":"wpkh('$PRIVKEY')#'$checksum'","timestamp":0,"internal":false}]'
# bitcoin-cli -datadir=$DATADIR importdescriptors $desc
fi
# Note: bitcoind will be started by run.sh, setup-signet.sh just prepares the configuration
# The wallet creation and key import will be done in run.sh after bitcoind starts

View File

@ -0,0 +1,21 @@
# Bitcoin RPC Configuration
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
BITCOIN_RPC_TIMEOUT=30000
# Dashboard Configuration
DASHBOARD_PORT=3020
DASHBOARD_HOST=0.0.0.0
# API Anchor Configuration (pour les tests d'ancrage)
ANCHOR_API_URL=http://localhost:3010
ANCHOR_API_KEY=your-api-key-here
# API Faucet Configuration
FAUCET_API_URL=http://localhost:3021
# Logging
LOG_LEVEL=info
NODE_ENV=production

6
signet-dashboard/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/
.env
*.log
.DS_Store
dist/
build/

348
signet-dashboard/README.md Normal file
View File

@ -0,0 +1,348 @@
# Dashboard de Supervision Bitcoin Signet
**Auteur** : Équipe 4NK
**Date** : 2026-01-23
**Version** : 1.0.0
## Description
Interface web de supervision et de test pour Bitcoin Signet. Ce dashboard permet de :
- Surveiller l'état de la blockchain Signet en temps réel
- Tester l'API d'ancrage avec génération de hash
- Utiliser le faucet pour recevoir des sats
## Caractéristiques
- **Port** : `3020`
- **Interface** : Web (HTML/CSS/JavaScript)
- **Backend** : Node.js/Express
- **Rafraîchissement** : Automatique toutes les 30 secondes
## Fonctionnalités
### Supervision de la Blockchain
- **Hauteur de bloc** : Affiche le nombre de blocs minés
- **Date du dernier bloc miné** : Timestamp du dernier bloc
- **Nombre de transactions dans le dernier bloc** : Compte des transactions
- **Balance du miner** :
- Balance mature (disponible)
- Balance immature (en attente de confirmation)
- **Nombre d'ancrages** : Compte approximatif des ancrages OP_RETURN
- **Nombre de pairs** : Nombre de connexions P2P actives
### Test de l'API d'Ancrage
- **Saisie de texte** : Entrer un texte à ancrer
- **Sélection de fichier** : Choisir un fichier à ancrer
- **Génération de hash** : Génération automatique du hash SHA256
- **Ancrage** : Envoi du hash à l'API d'ancrage (port 3010)
- **Résultat** : Affichage du TXID et du statut de l'ancrage
### Faucet
- **Adresse Bitcoin** : Saisie d'une adresse Signet valide
- **Montant** : 50 000 sats (0.0005 BTC) par défaut
- **Envoi** : Transaction automatique via l'API faucet (port 3021)
## Installation
### Prérequis
- Node.js >= 18.0.0
- Accès au nœud Bitcoin Signet (RPC sur port 38332)
- API d'ancrage accessible (port 3010)
- API faucet accessible (port 3021)
### Installation des Dépendances
```bash
cd signet-dashboard
npm install
```
### Configuration
1. Copier le fichier d'exemple :
```bash
cp .env.example .env
```
2. Éditer `.env` :
```bash
# Bitcoin RPC Configuration
BITCOIN_RPC_HOST=localhost
BITCOIN_RPC_PORT=38332
BITCOIN_RPC_USER=bitcoin
BITCOIN_RPC_PASSWORD=bitcoin
BITCOIN_RPC_TIMEOUT=30000
# Dashboard Configuration
DASHBOARD_PORT=3020
DASHBOARD_HOST=0.0.0.0
# API Anchor Configuration (pour les tests d'ancrage)
ANCHOR_API_URL=http://localhost:3010
ANCHOR_API_KEY=your-api-key-here
# API Faucet Configuration
FAUCET_API_URL=http://localhost:3021
# Logging
LOG_LEVEL=info
NODE_ENV=production
```
**Important** :
- `BITCOIN_RPC_HOST` : Si le dashboard est dans un conteneur Docker, utiliser l'IP du conteneur Bitcoin ou `host.docker.internal`
- `ANCHOR_API_KEY` : Clé API pour l'API d'ancrage (port 3010)
- `FAUCET_API_URL` : URL de l'API faucet (port 3021)
## Démarrage
### Mode Développement
```bash
npm run dev
```
### Mode Production
```bash
npm start
```
### Avec PM2 (recommandé pour production)
```bash
# Installer PM2 globalement
sudo npm install -g pm2
# Démarrer le dashboard avec PM2
pm2 start src/server.js --name signet-dashboard
# Sauvegarder la configuration PM2
pm2 save
# Configurer PM2 pour démarrer au boot
pm2 startup
# Suivre les instructions affichées
```
## Accès
Une fois démarré, le dashboard est accessible à l'adresse :
```
http://localhost:3020
```
## Architecture
```
Client (Navigateur)
↓ HTTP
Dashboard (port 3020)
↓ RPC
Bitcoin Signet Node (port 38332)
↓ HTTP
API Anchor (port 3010)
↓ HTTP
API Faucet (port 3021)
```
## API Endpoints
### GET /api/blockchain/info
Obtient les informations sur la blockchain.
**Réponse** :
```json
{
"chain": "signet",
"blocks": 1234,
"bestblockhash": "...",
...
}
```
### GET /api/blockchain/latest-block
Obtient les informations sur le dernier bloc miné.
**Réponse** :
```json
{
"hash": "...",
"height": 1234,
"time": 1234567890,
"tx_count": 5,
...
}
```
### GET /api/wallet/balance
Obtient le solde du wallet (mature et immature).
**Réponse** :
```json
{
"mature": 1.5,
"immature": 0.1,
"unconfirmed": 0.0,
"total": 1.6
}
```
### GET /api/network/peers
Obtient le nombre de pairs connectés.
**Réponse** :
```json
{
"connections": 5,
"peers": [...]
}
```
### GET /api/anchor/count
Obtient le nombre d'ancrages (approximatif).
**Réponse** :
```json
{
"count": 42
}
```
### POST /api/hash/generate
Génère un hash SHA256 à partir d'un texte ou d'un fichier.
**Body** :
```json
{
"text": "Texte à hasher"
}
```
ou
```json
{
"fileContent": "Contenu du fichier"
}
```
**Réponse** :
```json
{
"hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}
```
### POST /api/anchor/test
Teste l'ancrage d'un document via l'API d'ancrage.
**Body** :
```json
{
"hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}
```
**Réponse** :
```json
{
"txid": "...",
"status": "confirmed",
"confirmations": 1,
"block_height": 1234
}
```
## Configuration Nginx (sur proxy 192.168.1.100)
Ajouter dans la configuration nginx du proxy :
```nginx
# Dashboard Signet
server {
listen 443 ssl http2;
server_name dashboard.signet.4nkweb.com;
ssl_certificate /etc/letsencrypt/live/dashboard.signet.4nkweb.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dashboard.signet.4nkweb.com/privkey.pem;
location / {
proxy_pass http://192.168.1.103:3020;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
```
**Note** : Remplacer `192.168.1.103` par l'IP du serveur où le dashboard est déployé.
## Sécurité
- Le dashboard est accessible publiquement (pas d'authentification)
- Les appels à l'API d'ancrage nécessitent une clé API (configurée dans `.env`)
- Les appels à l'API faucet sont publics (gestion de rate limiting recommandée côté API faucet)
## Dépannage
### Le dashboard ne se charge pas
- Vérifier que le serveur est démarré : `pm2 status` ou `ps aux | grep node`
- Vérifier les logs : `pm2 logs signet-dashboard`
- Vérifier que le port 3020 n'est pas utilisé : `netstat -tlnp | grep 3020`
### Les données ne se mettent pas à jour
- Vérifier la connexion RPC Bitcoin : `curl http://localhost:3020/api/blockchain/info`
- Vérifier les logs pour les erreurs RPC
- Vérifier que le nœud Bitcoin est accessible
### L'ancrage ne fonctionne pas
- Vérifier que l'API d'ancrage est accessible : `curl http://localhost:3010/health`
- Vérifier que la clé API est correcte dans `.env`
- Vérifier les logs du dashboard pour les erreurs
### Le faucet ne fonctionne pas
- Vérifier que l'API faucet est accessible : `curl http://localhost:3021/health`
- Vérifier que le wallet a suffisamment de fonds
- Vérifier les logs de l'API faucet
## Structure des Fichiers
```
signet-dashboard/
├── package.json
├── README.md
├── .env.example
├── src/
│ ├── server.js # Serveur Express
│ ├── bitcoin-rpc.js # Client Bitcoin RPC
│ └── logger.js # Logger
└── public/
├── index.html # Interface web
├── styles.css # Styles CSS
└── app.js # JavaScript client
```
## Licence
MIT

1602
signet-dashboard/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
{
"name": "bitcoin-signet-dashboard",
"version": "1.0.0",
"description": "Dashboard de supervision et faucet pour Bitcoin Signet",
"main": "src/server.js",
"type": "module",
"scripts": {
"start": "node src/server.js",
"dev": "node --watch src/server.js"
},
"keywords": [
"bitcoin",
"signet",
"dashboard",
"faucet",
"supervision"
],
"author": "Équipe 4NK",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"bitcoin-core": "^4.2.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@ -0,0 +1,361 @@
/**
* Application JavaScript pour le Dashboard Bitcoin Signet
*/
const API_BASE_URL = window.location.origin;
// Utiliser le même origin pour le faucet (sera configuré via le proxy)
// Si on est sur dashboard.certificator.4nkweb.com, utiliser faucet.certificator.4nkweb.com
let FAUCET_API_URL;
if (window.location.hostname.includes('dashboard.certificator.4nkweb.com')) {
FAUCET_API_URL = window.location.origin.replace('dashboard.certificator.4nkweb.com', 'faucet.certificator.4nkweb.com');
} else if (window.location.hostname.includes('localhost') || window.location.hostname === '127.0.0.1') {
FAUCET_API_URL = 'http://localhost:3021';
} else {
FAUCET_API_URL = window.location.origin;
}
let selectedFile = null;
// Initialisation
document.addEventListener('DOMContentLoaded', () => {
loadData();
setInterval(loadData, 30000); // Rafraîchir toutes les 30 secondes
});
/**
* Charge toutes les données de supervision
*/
async function loadData() {
try {
await Promise.all([
loadBlockchainInfo(),
loadLatestBlock(),
loadWalletBalance(),
loadAnchorCount(),
loadNetworkPeers(),
]);
updateLastUpdateTime();
} catch (error) {
console.error('Error loading data:', error);
}
}
/**
* Charge les informations de la blockchain
*/
async function loadBlockchainInfo() {
try {
const response = await fetch(`${API_BASE_URL}/api/blockchain/info`);
const data = await response.json();
document.getElementById('block-height').textContent = data.blocks || 0;
} catch (error) {
console.error('Error loading blockchain info:', error);
document.getElementById('block-height').textContent = 'Erreur';
}
}
/**
* Charge les informations du dernier bloc
*/
async function loadLatestBlock() {
try {
const response = await fetch(`${API_BASE_URL}/api/blockchain/latest-block`);
const data = await response.json();
if (data.time) {
const date = new Date(data.time * 1000);
document.getElementById('last-block-time').textContent = date.toLocaleString('fr-FR');
} else {
document.getElementById('last-block-time').textContent = 'Aucun bloc';
}
document.getElementById('last-block-tx-count').textContent = data.tx_count || 0;
} catch (error) {
console.error('Error loading latest block:', error);
document.getElementById('last-block-time').textContent = 'Erreur';
document.getElementById('last-block-tx-count').textContent = 'Erreur';
}
}
/**
* Charge le solde du wallet
*/
async function loadWalletBalance() {
try {
const response = await fetch(`${API_BASE_URL}/api/wallet/balance`);
const data = await response.json();
document.getElementById('balance-mature').textContent = formatBTC(data.mature || 0);
document.getElementById('balance-immature').textContent = formatBTC(data.immature || 0);
} catch (error) {
console.error('Error loading wallet balance:', error);
document.getElementById('balance-mature').textContent = 'Erreur';
document.getElementById('balance-immature').textContent = 'Erreur';
}
}
/**
* Charge le nombre d'ancrages
*/
async function loadAnchorCount() {
try {
const response = await fetch(`${API_BASE_URL}/api/anchor/count`);
const data = await response.json();
document.getElementById('anchor-count').textContent = data.count || 0;
} catch (error) {
console.error('Error loading anchor count:', error);
document.getElementById('anchor-count').textContent = 'Erreur';
}
}
/**
* Charge les informations sur les pairs
*/
async function loadNetworkPeers() {
try {
const response = await fetch(`${API_BASE_URL}/api/network/peers`);
const data = await response.json();
document.getElementById('peer-count').textContent = data.connections || 0;
} catch (error) {
console.error('Error loading network peers:', error);
document.getElementById('peer-count').textContent = 'Erreur';
}
}
/**
* Formate un montant en BTC
*/
function formatBTC(btc) {
if (btc === 0) return '0 BTC';
if (btc < 0.000001) return `${(btc * 100000000).toFixed(0)} sats`;
return `${btc.toFixed(8)} BTC`;
}
/**
* Met à jour l'heure de dernière mise à jour
*/
function updateLastUpdateTime() {
const now = new Date();
document.getElementById('last-update').textContent = now.toLocaleString('fr-FR');
}
/**
* Change d'onglet
*/
function switchTab(tab, buttonElement) {
// Désactiver tous les onglets
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.remove('active');
});
// Activer l'onglet sélectionné
document.getElementById(`${tab}-tab`).classList.add('active');
// Activer le bouton correspondant
if (buttonElement) {
buttonElement.classList.add('active');
}
}
/**
* Gère la sélection de fichier
*/
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
selectedFile = file;
const fileInfo = document.getElementById('file-info');
fileInfo.innerHTML = `
<strong>Fichier sélectionné :</strong> ${file.name}<br>
<strong>Taille :</strong> ${formatFileSize(file.size)}<br>
<strong>Type :</strong> ${file.type || 'Non spécifié'}
`;
}
}
/**
* Formate la taille d'un fichier
*/
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
/**
* Génère le hash depuis le texte
*/
async function generateHashFromText() {
const text = document.getElementById('anchor-text').value;
if (!text.trim()) {
showResult('anchor-result', 'error', 'Veuillez entrer un texte à ancrer.');
return;
}
try {
const response = await fetch(`${API_BASE_URL}/api/hash/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text }),
});
const data = await response.json();
if (data.hash) {
document.getElementById('anchor-hash').value = data.hash;
showResult('anchor-result', 'success', `Hash généré avec succès : ${data.hash}`);
} else {
showResult('anchor-result', 'error', 'Erreur lors de la génération du hash.');
}
} catch (error) {
showResult('anchor-result', 'error', `Erreur : ${error.message}`);
}
}
/**
* Génère le hash depuis le fichier
*/
async function generateHashFromFile() {
if (!selectedFile) {
showResult('anchor-result', 'error', 'Veuillez sélectionner un fichier.');
return;
}
try {
const reader = new FileReader();
reader.onload = async (e) => {
const fileContent = e.target.result;
try {
const response = await fetch(`${API_BASE_URL}/api/hash/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ fileContent }),
});
const data = await response.json();
if (data.hash) {
document.getElementById('anchor-hash').value = data.hash;
showResult('anchor-result', 'success', `Hash généré avec succès : ${data.hash}`);
} else {
showResult('anchor-result', 'error', 'Erreur lors de la génération du hash.');
}
} catch (error) {
showResult('anchor-result', 'error', `Erreur : ${error.message}`);
}
};
reader.readAsText(selectedFile);
} catch (error) {
showResult('anchor-result', 'error', `Erreur lors de la lecture du fichier : ${error.message}`);
}
}
/**
* Ancre le document
*/
async function anchorDocument() {
const hash = document.getElementById('anchor-hash').value;
if (!hash || !/^[0-9a-fA-F]{64}$/.test(hash)) {
showResult('anchor-result', 'error', 'Veuillez générer un hash valide (64 caractères hexadécimaux).');
return;
}
try {
showResult('anchor-result', 'info', 'Ancrage en cours...');
const response = await fetch(`${API_BASE_URL}/api/anchor/test`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ hash }),
});
const data = await response.json();
if (response.ok && data.txid) {
showResult('anchor-result', 'success',
`Document ancré avec succès !<br>
<strong>TXID :</strong> ${data.txid}<br>
<strong>Statut :</strong> ${data.status}<br>
<strong>Confirmations :</strong> ${data.confirmations || 0}`);
// Recharger le nombre d'ancrages après un court délai
setTimeout(loadAnchorCount, 2000);
} else {
showResult('anchor-result', 'error', data.message || data.error || 'Erreur lors de l\'ancrage.');
}
} catch (error) {
showResult('anchor-result', 'error', `Erreur : ${error.message}`);
}
}
/**
* Demande des sats via le faucet
*/
async function requestFaucet() {
const address = document.getElementById('faucet-address').value.trim();
if (!address) {
showResult('faucet-result', 'error', 'Veuillez entrer une adresse Bitcoin.');
return;
}
// Validation basique de l'adresse
const addressPattern = /^(tb1|bcrt1|2|3)[a-zA-HJ-NP-Z0-9]{25,62}$/;
if (!addressPattern.test(address)) {
showResult('faucet-result', 'error', 'Adresse Bitcoin invalide.');
return;
}
try {
showResult('faucet-result', 'info', 'Demande en cours...');
const response = await fetch(`${FAUCET_API_URL}/api/faucet/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ address }),
});
const data = await response.json();
if (response.ok && data.txid) {
showResult('faucet-result', 'success',
`50 000 sats envoyés avec succès !<br>
<strong>TXID :</strong> ${data.txid}<br>
<strong>Montant :</strong> ${data.amount || '50000'} sats<br>
<strong>Statut :</strong> ${data.status || 'En attente de confirmation'}`);
} else {
showResult('faucet-result', 'error', data.message || data.error || 'Erreur lors de la demande.');
}
} catch (error) {
showResult('faucet-result', 'error', `Erreur : ${error.message}`);
}
}
/**
* Affiche un résultat
*/
function showResult(elementId, type, message) {
const element = document.getElementById(elementId);
element.className = `result ${type}`;
element.innerHTML = message;
}

View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bitcoin Signet - Dashboard de Supervision</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<h1>Bitcoin Signet - Dashboard de Supervision</h1>
<p class="subtitle">Surveillance de la blockchain et outils de test</p>
</header>
<main>
<!-- Section Supervision -->
<section class="supervision-section">
<h2>État de la Blockchain</h2>
<div class="grid">
<div class="card">
<h3>Hauteur de Bloc</h3>
<p class="value" id="block-height">-</p>
</div>
<div class="card">
<h3>Dernier Bloc Miné</h3>
<p class="value" id="last-block-time">-</p>
</div>
<div class="card">
<h3>Transactions dans le Dernier Bloc</h3>
<p class="value" id="last-block-tx-count">-</p>
</div>
<div class="card">
<h3>Balance Mature</h3>
<p class="value" id="balance-mature">-</p>
</div>
<div class="card">
<h3>Balance Immature</h3>
<p class="value" id="balance-immature">-</p>
</div>
<div class="card">
<h3>Nombre d'Ancrages</h3>
<p class="value" id="anchor-count">-</p>
</div>
<div class="card">
<h3>Nombre de Pairs</h3>
<p class="value" id="peer-count">-</p>
</div>
</div>
</section>
<!-- Section Test d'Ancrage -->
<section class="anchor-section">
<h2>Test de l'API d'Ancrage</h2>
<div class="card">
<div class="tabs">
<button class="tab-button active" onclick="switchTab('text', this)">Saisie de Texte</button>
<button class="tab-button" onclick="switchTab('file', this)">Sélection de Fichier</button>
</div>
<div id="text-tab" class="tab-content active">
<label for="anchor-text">Texte à ancrer :</label>
<textarea id="anchor-text" rows="5" placeholder="Entrez le texte à ancrer..."></textarea>
<button onclick="generateHashFromText()">Générer le Hash</button>
</div>
<div id="file-tab" class="tab-content">
<label for="anchor-file">Fichier à ancrer :</label>
<input type="file" id="anchor-file" onchange="handleFileSelect(event)">
<div id="file-info" class="file-info"></div>
<button onclick="generateHashFromFile()">Générer le Hash</button>
</div>
<div class="hash-section">
<label for="anchor-hash">Hash SHA256 :</label>
<input type="text" id="anchor-hash" placeholder="Le hash sera généré automatiquement..." readonly>
<button onclick="anchorDocument()">Ancrer le Document</button>
</div>
<div id="anchor-result" class="result"></div>
</div>
</section>
<!-- Section Faucet -->
<section class="faucet-section">
<h2>Faucet Bitcoin Signet</h2>
<div class="card">
<p>Recevez 50 000 sats (0.0005 BTC) sur votre adresse Bitcoin Signet</p>
<label for="faucet-address">Adresse Bitcoin :</label>
<input type="text" id="faucet-address" placeholder="tb1q..." pattern="^(tb1|bcrt1|2|3)[a-zA-HJ-NP-Z0-9]{25,62}$">
<button onclick="requestFaucet()">Demander des Sats</button>
<div id="faucet-result" class="result"></div>
</div>
</section>
</main>
<footer>
<p>Bitcoin Signet Dashboard - Équipe 4NK</p>
<p>Dernière mise à jour : <span id="last-update">-</span></p>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>

View File

@ -0,0 +1,267 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #f7931a;
--secondary-color: #1a1a1a;
--background-color: #f5f5f5;
--card-background: #ffffff;
--text-color: #333333;
--border-color: #e0e0e0;
--success-color: #28a745;
--error-color: #dc3545;
--warning-color: #ffc107;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 40px;
padding: 30px 0;
background: linear-gradient(135deg, var(--primary-color), #ff6b35);
color: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.2em;
opacity: 0.9;
}
main {
margin-bottom: 40px;
}
section {
margin-bottom: 40px;
}
section h2 {
font-size: 1.8em;
margin-bottom: 20px;
color: var(--secondary-color);
border-bottom: 3px solid var(--primary-color);
padding-bottom: 10px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: var(--card-background);
border-radius: 10px;
padding: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.card h3 {
font-size: 1.1em;
margin-bottom: 15px;
color: var(--secondary-color);
font-weight: 600;
}
.value {
font-size: 2em;
font-weight: bold;
color: var(--primary-color);
word-break: break-all;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 2px solid var(--border-color);
}
.tab-button {
padding: 10px 20px;
background: none;
border: none;
cursor: pointer;
font-size: 1em;
color: var(--text-color);
border-bottom: 3px solid transparent;
transition: all 0.3s;
}
.tab-button:hover {
color: var(--primary-color);
}
.tab-button.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
font-weight: 600;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--secondary-color);
}
input[type="text"],
input[type="file"],
textarea {
width: 100%;
padding: 12px;
border: 2px solid var(--border-color);
border-radius: 5px;
font-size: 1em;
margin-bottom: 15px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
textarea:focus {
outline: none;
border-color: var(--primary-color);
}
textarea {
resize: vertical;
min-height: 100px;
font-family: monospace;
}
button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 12px 24px;
border-radius: 5px;
font-size: 1em;
cursor: pointer;
transition: background-color 0.3s;
font-weight: 600;
}
button:hover {
background-color: #e8840a;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.hash-section {
margin-top: 20px;
padding-top: 20px;
border-top: 2px solid var(--border-color);
}
.file-info {
margin: 15px 0;
padding: 10px;
background-color: var(--background-color);
border-radius: 5px;
font-size: 0.9em;
}
.result {
margin-top: 20px;
padding: 15px;
border-radius: 5px;
display: none;
}
.result.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
display: block;
}
.result.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
display: block;
}
.result.info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
display: block;
}
footer {
text-align: center;
padding: 20px;
background-color: var(--card-background);
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #666;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
header h1 {
font-size: 2em;
}
.container {
padding: 10px;
}
}

View File

@ -0,0 +1,199 @@
/**
* Client Bitcoin RPC pour le dashboard
*
* Gère la connexion et les appels RPC vers le nœud Bitcoin Signet
*/
import Client from 'bitcoin-core';
import { logger } from './logger.js';
class BitcoinRPC {
constructor() {
this.client = new Client({
host: process.env.BITCOIN_RPC_HOST || 'localhost',
port: parseInt(process.env.BITCOIN_RPC_PORT || '38332'),
username: process.env.BITCOIN_RPC_USER || 'bitcoin',
password: process.env.BITCOIN_RPC_PASSWORD || 'bitcoin',
timeout: parseInt(process.env.BITCOIN_RPC_TIMEOUT || '30000'),
});
}
/**
* Obtient les informations sur la blockchain
* @returns {Promise<Object>} Informations sur la blockchain
*/
async getBlockchainInfo() {
try {
return await this.client.getBlockchainInfo();
} catch (error) {
logger.error('Error getting blockchain info', { error: error.message });
throw new Error(`Failed to get blockchain info: ${error.message}`);
}
}
/**
* Obtient le dernier bloc miné
* @returns {Promise<Object>} Informations sur le dernier bloc
*/
async getLatestBlock() {
try {
const blockchainInfo = await this.client.getBlockchainInfo();
const bestBlockHash = blockchainInfo.bestblockhash;
const block = await this.client.getBlock(bestBlockHash, 2); // Verbose level 2
return {
hash: block.hash,
height: block.height,
time: block.time,
mediantime: block.mediantime,
tx_count: block.tx ? block.tx.length : 0,
size: block.size,
weight: block.weight,
};
} catch (error) {
logger.error('Error getting latest block', { error: error.message });
throw new Error(`Failed to get latest block: ${error.message}`);
}
}
/**
* Obtient le solde du wallet (mature et immature)
* @returns {Promise<Object>} Solde du wallet
*/
async getWalletBalance() {
try {
// Utiliser command() pour appeler directement getbalances() qui retourne mature, immature, et unconfirmed
// Si getbalances() n'est pas disponible, utiliser getBalance() avec différents minconf
let balances;
try {
// Utiliser command() pour appeler getbalances() directement (méthode RPC de Bitcoin Core)
balances = await this.client.command('getbalances');
// getbalances() retourne { "mine": { "trusted": ..., "untrusted_pending": ..., "immature": ... } }
const mine = balances.mine || {};
return {
mature: mine.trusted || 0,
immature: mine.immature || 0,
unconfirmed: mine.untrusted_pending || 0,
total: (mine.trusted || 0) + (mine.immature || 0) + (mine.untrusted_pending || 0),
};
} catch (error) {
// Fallback si getbalances() n'est pas disponible
logger.debug('getbalances() not available, using getBalance()', { error: error.message });
// getBalance() retourne le solde mature (confirmé avec au moins 1 confirmation)
const balance = await this.client.getBalance();
// getBalance avec minconf=0 retourne le solde total (mature + immature)
const totalBalance = await this.client.getBalance('*', 0);
// Calculer le solde immature
const immatureBalance = Math.max(0, totalBalance - balance);
// Obtenir les transactions non confirmées depuis listUnspent
let unconfirmedBalance = 0;
try {
const unspent = await this.client.listUnspent(0); // 0 = inclure les non confirmés
for (const utxo of unspent) {
if (utxo.confirmations === 0) {
unconfirmedBalance += utxo.amount;
}
}
} catch (error) {
// Si listUnspent échoue, unconfirmedBalance reste à 0
logger.debug('Could not get unconfirmed balance', { error: error.message });
}
return {
mature: balance,
immature: immatureBalance,
unconfirmed: unconfirmedBalance,
total: totalBalance + unconfirmedBalance,
};
}
} catch (error) {
logger.error('Error getting wallet balance', { error: error.message });
throw new Error(`Failed to get wallet balance: ${error.message}`);
}
}
/**
* Obtient le nombre de pairs connectés
* @returns {Promise<Object>} Informations sur les pairs
*/
async getNetworkPeers() {
try {
const networkInfo = await this.client.getNetworkInfo();
const peerInfo = await this.client.getPeerInfo();
return {
connections: networkInfo.connections,
peers: peerInfo.map(peer => ({
addr: peer.addr,
services: peer.services,
version: peer.version,
subver: peer.subver,
})),
};
} catch (error) {
logger.error('Error getting network peers', { error: error.message });
throw new Error(`Failed to get network peers: ${error.message}`);
}
}
/**
* Obtient le nombre d'ancrages (approximatif en comptant les transactions OP_RETURN)
* @returns {Promise<number>} Nombre d'ancrages
*/
async getAnchorCount() {
try {
const blockchainInfo = await this.client.getBlockchainInfo();
const currentHeight = blockchainInfo.blocks;
// Rechercher dans les 1000 derniers blocs pour compter les ancrages
let anchorCount = 0;
const searchRange = Math.min(1000, currentHeight + 1);
for (let height = currentHeight; height >= Math.max(0, currentHeight - searchRange); height--) {
try {
const blockHash = await this.client.getBlockHash(height);
const block = await this.client.getBlock(blockHash, 2);
if (block.tx) {
for (const tx of block.tx) {
try {
const rawTx = await this.client.getRawTransaction(tx.txid, true);
// Vérifier si la transaction contient un OP_RETURN avec "ANCHOR:"
for (const output of rawTx.vout || []) {
if (output.scriptPubKey && output.scriptPubKey.hex) {
const scriptHex = output.scriptPubKey.hex;
const anchorPrefix = Buffer.from('ANCHOR:', 'utf8').toString('hex');
if (scriptHex.includes(anchorPrefix)) {
anchorCount++;
break; // Compter une seule fois par transaction
}
}
}
} catch (error) {
// Continuer avec la transaction suivante
logger.debug('Error checking transaction for anchor', { txid: tx.txid, error: error.message });
}
}
}
} catch (error) {
// Continuer avec le bloc suivant
logger.debug('Error checking block for anchors', { height, error: error.message });
}
}
return anchorCount;
} catch (error) {
logger.error('Error getting anchor count', { error: error.message });
throw new Error(`Failed to get anchor count: ${error.message}`);
}
}
}
// Export singleton
export const bitcoinRPC = new BitcoinRPC();

View File

@ -0,0 +1,30 @@
/**
* Logger simple pour le dashboard
*/
const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
const levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
function log(level, message, meta = {}) {
const levelNum = levels[level] || 2;
const currentLevel = levels[LOG_LEVEL] || 2;
if (levelNum <= currentLevel) {
const timestamp = new Date().toISOString();
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`);
}
}
export const logger = {
error: (message, meta) => log('error', message, meta),
warn: (message, meta) => log('warn', message, meta),
info: (message, meta) => log('info', message, meta),
debug: (message, meta) => log('debug', message, meta),
};

View File

@ -0,0 +1,208 @@
#!/usr/bin/env node
/**
* Serveur web de supervision et faucet Bitcoin Signet
*
* Ce serveur fournit une interface web pour :
* - Superviser l'état de la blockchain Signet
* - Tester l'API d'ancrage
* - Utiliser le faucet pour recevoir des sats
*
* Port: 3020
*/
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import crypto from 'crypto';
import { bitcoinRPC } from './bitcoin-rpc.js';
import { logger } from './logger.js';
// Charger les variables d'environnement
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const PORT = process.env.DASHBOARD_PORT || 3020;
const HOST = process.env.DASHBOARD_HOST || '0.0.0.0';
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Servir les fichiers statiques
app.use(express.static(join(__dirname, '../public')));
// Middleware de logging
app.use((req, res, next) => {
logger.info(`${req.method} ${req.path}`, {
ip: req.ip,
userAgent: req.get('user-agent'),
});
next();
});
// Route pour la page principale
app.get('/', (req, res) => {
res.sendFile(join(__dirname, '../public/index.html'));
});
// API Routes
app.get('/api/blockchain/info', async (req, res) => {
try {
const blockchainInfo = await bitcoinRPC.getBlockchainInfo();
res.json(blockchainInfo);
} catch (error) {
logger.error('Error getting blockchain info', { error: error.message });
res.status(500).json({ error: error.message });
}
});
app.get('/api/blockchain/latest-block', async (req, res) => {
try {
const latestBlock = await bitcoinRPC.getLatestBlock();
res.json(latestBlock);
} catch (error) {
logger.error('Error getting latest block', { error: error.message });
res.status(500).json({ error: error.message });
}
});
app.get('/api/wallet/balance', async (req, res) => {
try {
const balance = await bitcoinRPC.getWalletBalance();
res.json(balance);
} catch (error) {
logger.error('Error getting wallet balance', { error: error.message });
res.status(500).json({ error: error.message });
}
});
app.get('/api/network/peers', async (req, res) => {
try {
const peers = await bitcoinRPC.getNetworkPeers();
res.json(peers);
} catch (error) {
logger.error('Error getting network peers', { error: error.message });
res.status(500).json({ error: error.message });
}
});
app.get('/api/anchor/count', async (req, res) => {
try {
const count = await bitcoinRPC.getAnchorCount();
res.json({ count });
} catch (error) {
logger.error('Error getting anchor count', { error: error.message });
res.status(500).json({ error: error.message });
}
});
// Route pour générer un hash SHA256
app.post('/api/hash/generate', (req, res) => {
try {
const { text, fileContent } = req.body;
if (!text && !fileContent) {
return res.status(400).json({ error: 'text or fileContent is required' });
}
const content = text || fileContent;
const hash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
res.json({ hash });
} catch (error) {
logger.error('Error generating hash', { error: error.message });
res.status(500).json({ error: error.message });
}
});
// Route pour tester l'ancrage (appelle l'API d'ancrage externe)
app.post('/api/anchor/test', async (req, res) => {
try {
const { hash } = req.body;
if (!hash) {
return res.status(400).json({ error: 'hash is required' });
}
if (!/^[0-9a-fA-F]{64}$/.test(hash)) {
return res.status(400).json({ error: 'hash must be a 64 character hexadecimal string' });
}
// Utiliser l'URL HTTPS du sous-domaine si disponible, sinon localhost
const anchorApiUrl = process.env.ANCHOR_API_URL ||
(process.env.NODE_ENV === 'production'
? 'https://anchorage.certificator.4nkweb.com'
: 'http://localhost:3010');
const anchorApiKey = process.env.ANCHOR_API_KEY || '';
const response = await fetch(`${anchorApiUrl}/api/anchor/document`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': anchorApiKey,
},
body: JSON.stringify({ hash }),
});
const result = await response.json();
if (!response.ok) {
return res.status(response.status).json(result);
}
res.json(result);
} catch (error) {
logger.error('Error testing anchor', { error: error.message });
res.status(500).json({ error: error.message });
}
});
// Gestion des erreurs
app.use((err, req, res, next) => {
logger.error('Unhandled error', { error: err.message, stack: err.stack });
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : 'An error occurred',
});
});
// Gestion des routes non trouvées
app.use((req, res) => {
res.status(404).json({
error: 'Not Found',
message: `Route ${req.method} ${req.path} not found`,
});
});
// Démarrage du serveur
const server = app.listen(PORT, HOST, () => {
logger.info(`Dashboard Bitcoin Signet démarré`, {
host: HOST,
port: PORT,
environment: process.env.NODE_ENV || 'production',
});
});
// Gestion de l'arrêt propre
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down gracefully');
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down gracefully');
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
});

21
signet-dashboard/start.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
cd "$(dirname "$0")"
export BITCOIN_RPC_HOST=${BITCOIN_RPC_HOST:-localhost}
export BITCOIN_RPC_PORT=${BITCOIN_RPC_PORT:-38332}
export BITCOIN_RPC_USER=${BITCOIN_RPC_USER:-bitcoin}
export BITCOIN_RPC_PASSWORD=${BITCOIN_RPC_PASSWORD:-bitcoin}
export DASHBOARD_PORT=${DASHBOARD_PORT:-3020}
export DASHBOARD_HOST=${DASHBOARD_HOST:-0.0.0.0}
export ANCHOR_API_URL=${ANCHOR_API_URL:-http://localhost:3010}
export ANCHOR_API_KEY=${ANCHOR_API_KEY:-}
export FAUCET_API_URL=${FAUCET_API_URL:-http://localhost:3021}
export LOG_LEVEL=${LOG_LEVEL:-info}
export NODE_ENV=${NODE_ENV:-production}
if [ ! -f node_modules/.bin/node ]; then
echo "Installation des dépendances..."
npm install
fi
echo "Démarrage du Dashboard sur le port $DASHBOARD_PORT..."
node src/server.js

275
update-signet.sh Executable file
View File

@ -0,0 +1,275 @@
#!/bin/bash
set -euo pipefail
# Script de mise à jour du Bitcoin Signet Custom
# Auteur: Équipe 4NK
# Date: 2026-01-09
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Couleurs pour les messages
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Fonction d'affichage
info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Vérification des prérequis
check_prerequisites() {
info "Vérification des prérequis..."
if ! command -v docker &> /dev/null; then
error "Docker n'est pas installé"
exit 1
fi
if ! sudo docker ps &> /dev/null; then
error "Impossible d'accéder à Docker. Vérifiez les permissions."
exit 1
fi
if [ ! -f ".env" ]; then
error "Le fichier .env n'existe pas"
exit 1
fi
success "Prérequis vérifiés"
}
# Récupération de la version actuelle
get_current_version() {
if [ -f ".bitcoin-version" ]; then
cat .bitcoin-version
else
grep -oP 'BITCOIN_VERSION=\$\{BITCOIN_VERSION:-?\K[0-9]+\.[0-9]+' Dockerfile | head -1 || echo "unknown"
fi
}
# Récupération de la dernière version disponible
get_latest_version() {
local version
# Essayer plusieurs méthodes pour récupérer la version
version=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest 2>/dev/null | \
grep -oP '"tag_name": "\Kv?[0-9]+\.[0-9]+\.[0-9]+' | head -1 | sed 's/^v//')
if [ -z "$version" ]; then
version=$(curl -s https://bitcoincore.org/bin/ 2>/dev/null | \
grep -oP 'bitcoin-core-\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1)
fi
if [ -z "$version" ]; then
# Version par défaut si aucune méthode ne fonctionne
version="30.2"
warning "Impossible de récupérer la version automatiquement, utilisation de la version par défaut: $version"
fi
echo "$version"
}
# Sauvegarde avant mise à jour
backup_data() {
info "Sauvegarde des données avant mise à jour..."
local backup_dir="backups"
local backup_file="${backup_dir}/signet-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
mkdir -p "$backup_dir"
if sudo docker ps | grep -q bitcoin-signet-instance; then
info "Sauvegarde des données du conteneur..."
sudo docker exec bitcoin-signet-instance tar czf /tmp/bitcoin-backup.tar.gz /root/.bitcoin/ 2>/dev/null || true
sudo docker cp bitcoin-signet-instance:/tmp/bitcoin-backup.tar.gz "$backup_file" 2>/dev/null || true
fi
# Sauvegarder le .env
cp .env "${backup_dir}/.env.backup-$(date +%Y%m%d-%H%M%S)"
success "Sauvegarde effectuée: $backup_file"
}
# Mise à jour du Dockerfile
update_dockerfile() {
local new_version=$1
info "Mise à jour du Dockerfile avec Bitcoin Core $new_version..."
# Mettre à jour la version dans le Dockerfile
sed -i "s/ARG BITCOIN_VERSION=\${BITCOIN_VERSION:-[0-9]\+\.[0-9]\+}/ARG BITCOIN_VERSION=\${BITCOIN_VERSION:-${new_version}}/" Dockerfile
success "Dockerfile mis à jour"
}
# Reconstruction de l'image
rebuild_image() {
info "Reconstruction de l'image Docker..."
if sudo docker build -t bitcoin-signet .; then
success "Image reconstruite avec succès"
else
error "Échec de la reconstruction de l'image"
exit 1
fi
}
# Redémarrage du conteneur
restart_container() {
info "Redémarrage du conteneur..."
# Arrêter le conteneur existant
if sudo docker ps | grep -q bitcoin-signet-instance; then
info "Arrêt du conteneur existant..."
sudo docker stop bitcoin-signet-instance
sudo docker rm bitcoin-signet-instance
fi
# Démarrer le nouveau conteneur
info "Démarrage du nouveau conteneur..."
sudo docker run --env-file .env -d \
--name bitcoin-signet-instance \
-p 38332:38332 \
-p 38333:38333 \
-p 28332:28332 \
-p 28333:28333 \
-p 28334:28334 \
bitcoin-signet
success "Conteneur redémarré"
}
# Vérification post-mise à jour
verify_update() {
info "Vérification de la mise à jour..."
sleep 10
# Vérifier que le conteneur est en cours d'exécution
if ! sudo docker ps | grep -q bitcoin-signet-instance; then
error "Le conteneur n'est pas en cours d'exécution"
return 1
fi
# Vérifier la version de Bitcoin
local version_info
version_info=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin -version 2>/dev/null || \
sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo 2>/dev/null | grep -oP '"subversion": "\K[^"]+' || echo "unknown")
if [ "$version_info" != "unknown" ]; then
success "Version Bitcoin détectée: $version_info"
else
warning "Impossible de vérifier la version, mais le conteneur fonctionne"
fi
# Vérifier l'état de la blockchain
local blockchain_info
blockchain_info=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo 2>/dev/null | grep -oP '"chain": "\K[^"]+' || echo "unknown")
if [ "$blockchain_info" = "signet" ]; then
success "Blockchain signet opérationnelle"
else
warning "État de la blockchain: $blockchain_info"
fi
}
# Fonction principale
main() {
local target_version="${1:-}"
local current_version
local latest_version
echo "=========================================="
echo " Mise à jour du Bitcoin Signet Custom"
echo "=========================================="
echo ""
check_prerequisites
current_version=$(get_current_version)
info "Version actuelle: $current_version"
if [ -n "$target_version" ]; then
latest_version="$target_version"
info "Version cible spécifiée: $latest_version"
else
info "Récupération de la dernière version disponible..."
latest_version=$(get_latest_version)
info "Dernière version disponible: $latest_version"
fi
if [ "$current_version" = "$latest_version" ]; then
success "Déjà à jour (version $current_version)"
exit 0
fi
echo ""
warning "ATTENTION: Cette mise à jour va:"
warning " 1. Arrêter le conteneur actuel"
warning " 2. Reconstruire l'image Docker"
warning " 3. Redémarrer le conteneur"
echo ""
read -p "Continuer? (o/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[OoYy]$ ]]; then
info "Mise à jour annulée"
exit 0
fi
echo ""
backup_data
echo ""
update_dockerfile "$latest_version"
echo ""
rebuild_image
echo ""
restart_container
echo ""
verify_update
echo ""
# Enregistrer la nouvelle version
echo "$latest_version" > .bitcoin-version
success "Version enregistrée: $latest_version"
echo ""
success "Mise à jour terminée avec succès!"
info "Pour voir les logs: sudo docker logs -f bitcoin-signet-instance"
info "Pour vérifier l'état: sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo"
}
# Gestion des arguments
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
echo "Usage: $0 [VERSION]"
echo ""
echo "Mise à jour du Bitcoin Signet Custom"
echo ""
echo "Arguments:"
echo " VERSION Version spécifique de Bitcoin Core (ex: 27.0)"
echo " Si non spécifié, utilise la dernière version disponible"
echo ""
echo "Options:"
echo " -h, --help Affiche cette aide"
echo ""
exit 0
fi
# Exécution du script principal
main "$@"