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:
parent
9f304317d6
commit
20d115a31c
1
.bitcoin-version
Normal file
1
.bitcoin-version
Normal file
@ -0,0 +1 @@
|
||||
30.2
|
||||
15
.env
15
.env
@ -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
|
||||
|
||||
28
Dockerfile
28
Dockerfile
@ -1,28 +1,30 @@
|
||||
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
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -qq --no-install-recommends ca-certificates dirmngr gosu wget libc6 procps python3
|
||||
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" && \
|
||||
wget -qO "${BITCOIN_FILE}" "${BITCOIN_URL}" && \
|
||||
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
|
||||
ENV BITCOIN_DIR /root/.bitcoin
|
||||
|
||||
ENV NBITS=${NBITS}
|
||||
ENV SIGNETCHALLENGE=${SIGNETCHALLENGE}
|
||||
@ -45,14 +47,14 @@ ENV ADDNODE=${ADDNODE:-""}
|
||||
ENV BLOCKPRODUCTIONDELAY=${BLOCKPRODUCTIONDELAY:-""}
|
||||
ENV MINERENABLED=${MINERENABLED:-"1"}
|
||||
ENV MINETO=${MINETO:-""}
|
||||
ENV EXTERNAL_IP=${EXTERNAL_IP:-""}
|
||||
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 --from=builder "/tmp/bin" /usr/local/bin
|
||||
COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /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
87
ETAT_SYSTEME.md
Normal 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
|
||||
```
|
||||
101
START_DASHBOARD_AND_FAUCET.md
Normal file
101
START_DASHBOARD_AND_FAUCET.md
Normal 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`
|
||||
BIN
__pycache__/minercpython-311.pyc
Normal file
BIN
__pycache__/minercpython-311.pyc
Normal file
Binary file not shown.
20
api-anchorage/.env.example
Normal file
20
api-anchorage/.env.example
Normal 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
7
api-anchorage/.gitignore
vendored
Normal 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
399
api-anchorage/DEPLOYMENT.md
Normal 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
362
api-anchorage/README.md
Normal 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
1602
api-anchorage/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
api-anchorage/package.json
Normal file
29
api-anchorage/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
374
api-anchorage/src/bitcoin-rpc.js
Normal file
374
api-anchorage/src/bitcoin-rpc.js
Normal 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();
|
||||
41
api-anchorage/src/logger.js
Normal file
41
api-anchorage/src/logger.js
Normal 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));
|
||||
}
|
||||
},
|
||||
};
|
||||
117
api-anchorage/src/routes/anchor.js
Normal file
117
api-anchorage/src/routes/anchor.js
Normal 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,
|
||||
});
|
||||
}
|
||||
});
|
||||
40
api-anchorage/src/routes/health.js
Normal file
40
api-anchorage/src/routes/health.js
Normal 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
121
api-anchorage/src/server.js
Normal 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);
|
||||
});
|
||||
});
|
||||
197
api-anchorage/src/test-client.js
Normal file
197
api-anchorage/src/test-client.js
Normal 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
17
api-faucet/.env.example
Normal 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
6
api-faucet/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
dist/
|
||||
build/
|
||||
306
api-faucet/README.md
Normal file
306
api-faucet/README.md
Normal 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
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
28
api-faucet/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
136
api-faucet/src/bitcoin-rpc.js
Normal file
136
api-faucet/src/bitcoin-rpc.js
Normal 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
30
api-faucet/src/logger.js
Normal 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),
|
||||
};
|
||||
75
api-faucet/src/routes/faucet.js
Normal file
75
api-faucet/src/routes/faucet.js
Normal 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,
|
||||
});
|
||||
}
|
||||
});
|
||||
43
api-faucet/src/routes/health.js
Normal file
43
api-faucet/src/routes/health.js
Normal 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
98
api-faucet/src/server.js
Normal 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
19
api-faucet/start.sh
Executable 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
234
configure-nginx-proxy.sh
Executable 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 ""
|
||||
80
docs/COMPATIBILITY_CHECK.md
Normal file
80
docs/COMPATIBILITY_CHECK.md
Normal 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
|
||||
595
docs/INSTALLATION_NEW_NODE.md
Normal file
595
docs/INSTALLATION_NEW_NODE.md
Normal 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
461
docs/INTERFACES.md
Normal 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
814
docs/MAINTENANCE.md
Normal 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
152
docs/PROMPT_IA_RECHERCHE.md
Normal 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
65
docs/README.md
Normal 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
104
docs/SOLUTION_MINING.md
Normal 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
|
||||
94
docs/TROUBLESHOOTING_MINING.md
Normal file
94
docs/TROUBLESHOOTING_MINING.md
Normal 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
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ if [[ "$ADDNODE" != "" ]]; then
|
||||
echo "addnode=$node"
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [[ "$I2PSAM" != "" ]]; then
|
||||
echo "i2psam=$I2PSAM"
|
||||
|
||||
@ -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
1
mempool
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 912f74bea073b2857b49d850cdd53be748188ccf
|
||||
4
mine.sh
4
mine.sh
@ -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
|
||||
55
miner
55
miner
@ -319,7 +319,7 @@ def seconds_to_hms(s):
|
||||
return out
|
||||
|
||||
def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson):
|
||||
this_interval = 0.000001
|
||||
this_interval = 0.000001
|
||||
return this_interval
|
||||
|
||||
def next_block_is_mine(last_hash, my_blocks):
|
||||
@ -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")
|
||||
|
||||
40
run.sh
40
run.sh
@ -3,8 +3,46 @@
|
||||
# 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=$(cat /root/.bitcoin/signet/debug.log | grep -m1 magic)
|
||||
magic=${magic:(-8)}
|
||||
echo $magic > /root/.bitcoin/MAGIC.txt
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
21
signet-dashboard/.env.example
Normal file
21
signet-dashboard/.env.example
Normal 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
6
signet-dashboard/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
dist/
|
||||
build/
|
||||
348
signet-dashboard/README.md
Normal file
348
signet-dashboard/README.md
Normal 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
1602
signet-dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
signet-dashboard/package.json
Normal file
29
signet-dashboard/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
361
signet-dashboard/public/app.js
Normal file
361
signet-dashboard/public/app.js
Normal 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;
|
||||
}
|
||||
105
signet-dashboard/public/index.html
Normal file
105
signet-dashboard/public/index.html
Normal 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>
|
||||
267
signet-dashboard/public/styles.css
Normal file
267
signet-dashboard/public/styles.css
Normal 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;
|
||||
}
|
||||
}
|
||||
199
signet-dashboard/src/bitcoin-rpc.js
Normal file
199
signet-dashboard/src/bitcoin-rpc.js
Normal 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();
|
||||
30
signet-dashboard/src/logger.js
Normal file
30
signet-dashboard/src/logger.js
Normal 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),
|
||||
};
|
||||
208
signet-dashboard/src/server.js
Normal file
208
signet-dashboard/src/server.js
Normal 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
21
signet-dashboard/start.sh
Executable 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
275
update-signet.sh
Executable 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 "$@"
|
||||
Loading…
x
Reference in New Issue
Block a user