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
|
# Mining Configuration
|
||||||
BLOCKPRODUCTIONDELAY=60
|
# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported)
|
||||||
|
BLOCKPRODUCTIONDELAY=600
|
||||||
MINERENABLED=1
|
MINERENABLED=1
|
||||||
NBITS=1e0377ae
|
NBITS=1e0377ae
|
||||||
PRIVKEY=cQ78Y7oUASnzbgK465VPug4CrfxJPe95ftsDqwMwTJU8j1NoY76C
|
PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS
|
||||||
MINETO=tb1qhcuywsgshgs2ukv0ae2gw9egrfqx944qh6eurq
|
MINETO=
|
||||||
SIGNETCHALLENGE=5121021dc5794f479c961e47fd3ec343ecd7bb43c3abb991fb1e104f1e2403019e5e7551ae
|
SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae
|
||||||
|
|
||||||
# RPC Configuration
|
# RPC Configuration
|
||||||
RPCUSER=easepay-signet
|
RPCUSER=bitcoin
|
||||||
RPCPASSWORD=Beautifulgirl456
|
RPCPASSWORD=bitcoin
|
||||||
|
|
||||||
UACOMMENT=Easepay
|
UACOMMENT=CustomSignet
|
||||||
|
|
||||||
# ZMQ Configuration
|
# ZMQ Configuration
|
||||||
ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332
|
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 && \
|
RUN apt-get update && \
|
||||||
apt-get install -qq --no-install-recommends ca-certificates dirmngr gosu wget libc6 procps python3
|
apt-get install -qq --no-install-recommends ca-certificates dirmngr gosu wget libc6 procps python3
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
|
|
||||||
# Install Bitcoin binaries based on platform
|
# Install Bitcoin binaries based on platform
|
||||||
RUN case $TARGETPLATFORM in \
|
RUN ARCH=$(uname -m) && \
|
||||||
linux/amd64) export TRIPLET="x86_64-linux-gnu";; \
|
case $ARCH in \
|
||||||
linux/arm64) export TRIPLET="aarch64-linux-gnu";; \
|
x86_64) export TRIPLET="x86_64-linux-gnu";; \
|
||||||
|
aarch64) export TRIPLET="aarch64-linux-gnu";; \
|
||||||
|
*) echo "Unsupported architecture: $ARCH" && exit 1;; \
|
||||||
esac && \
|
esac && \
|
||||||
BITCOIN_URL="https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \
|
BITCOIN_URL="https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \
|
||||||
BITCOIN_FILE="bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \
|
BITCOIN_FILE="bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \
|
||||||
wget -qO "${BITCOIN_FILE}" "${BITCOIN_URL}" && \
|
wget -qO "${BITCOIN_FILE}" "${BITCOIN_URL}" && \
|
||||||
mkdir -p bin && \
|
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"
|
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 NBITS=${NBITS}
|
||||||
ENV SIGNETCHALLENGE=${SIGNETCHALLENGE}
|
ENV SIGNETCHALLENGE=${SIGNETCHALLENGE}
|
||||||
@ -45,14 +47,14 @@ ENV ADDNODE=${ADDNODE:-""}
|
|||||||
ENV BLOCKPRODUCTIONDELAY=${BLOCKPRODUCTIONDELAY:-""}
|
ENV BLOCKPRODUCTIONDELAY=${BLOCKPRODUCTIONDELAY:-""}
|
||||||
ENV MINERENABLED=${MINERENABLED:-"1"}
|
ENV MINERENABLED=${MINERENABLED:-"1"}
|
||||||
ENV MINETO=${MINETO:-""}
|
ENV MINETO=${MINETO:-""}
|
||||||
ENV EXTERNAL_IP=${EXTERNAL_IP:-""}
|
ENV EXTERNAL_IP=${EXTERNAL_IP:-""}
|
||||||
|
|
||||||
VOLUME $BITCOIN_DIR
|
VOLUME $BITCOIN_DIR
|
||||||
EXPOSE 28332 28333 28334 38332 38333 38334
|
EXPOSE 28332 28333 28334 38332 38333 38334
|
||||||
RUN apt-get update && \
|
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
|
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
|
COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
RUN chmod +x /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
|
RUN chmod +x /usr/local/bin/*.sh
|
||||||
COPY rpcauth.py /usr/local/bin/rpcauth.py
|
COPY rpcauth.py /usr/local/bin/rpcauth.py
|
||||||
RUN chmod +x /usr/local/bin/rpcauth.py
|
RUN chmod +x /usr/local/bin/rpcauth.py
|
||||||
RUN pip3 install setuptools
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
|
|
||||||
CMD ["run.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
|
# Mining Configuration
|
||||||
|
# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported)
|
||||||
BLOCKPRODUCTIONDELAY=<Specify delay in seconds>
|
BLOCKPRODUCTIONDELAY=<Specify delay in seconds>
|
||||||
MINERENABLED=1
|
MINERENABLED=1
|
||||||
NBITS=<Your minimum difficulty value>
|
NBITS=<Your minimum difficulty value>
|
||||||
PRIVKEY=<Your private key for Signet, if available>
|
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>
|
MINETO=<Your mining address, if available>
|
||||||
SIGNETCHALLENGE=<Your Signet challenge>
|
SIGNETCHALLENGE=<Your Signet challenge>
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ if [[ "$ADDNODE" != "" ]]; then
|
|||||||
echo "addnode=$node"
|
echo "addnode=$node"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [[ "$I2PSAM" != "" ]]; then
|
if [[ "$I2PSAM" != "" ]]; then
|
||||||
echo "i2psam=$I2PSAM"
|
echo "i2psam=$I2PSAM"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
ATADIR=${DATADIR:-"regtest-temp"}
|
DATADIR=${DATADIR:-"regtest-temp"}
|
||||||
BITCOINCLI=${BITCOINCLI:-"bitcoin-cli -regtest -datadir=$DATADIR "}
|
BITCOINCLI=${BITCOINCLI:-"bitcoin-cli -regtest -datadir=$DATADIR "}
|
||||||
BITCOIND=${BITCOIND:-"bitcoind -datadir=$DATADIR -regtest -daemon "}
|
BITCOIND=${BITCOIND:-"bitcoind -datadir=$DATADIR -regtest -daemon "}
|
||||||
|
|
||||||
@ -33,9 +33,8 @@ if [[ "$MINERENABLED" == "1" && ("$SIGNETCHALLENGE" == "" || "$PRIVKEY" == "") ]
|
|||||||
$BITCOIND -wallet="temp"
|
$BITCOIND -wallet="temp"
|
||||||
#wait a bit for startup
|
#wait a bit for startup
|
||||||
sleep 5s
|
sleep 5s
|
||||||
#create wallet
|
#create wallet (Bitcoin Core 30+ requires descriptor wallets)
|
||||||
# todo, redo to work with descriptors
|
$BITCOINCLI -named createwallet wallet_name="tmp" descriptors=true
|
||||||
$BITCOINCLI -named createwallet wallet_name="tmp" descriptors=false
|
|
||||||
#export future signet seeding key data
|
#export future signet seeding key data
|
||||||
ADDR=$($BITCOINCLI getnewaddress)
|
ADDR=$($BITCOINCLI getnewaddress)
|
||||||
PRIVKEY=$($BITCOINCLI dumpprivkey $ADDR)
|
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
|
||||||
fi
|
fi
|
||||||
echo "Mine To:" $ADDR
|
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)
|
miner --cli="bitcoin-cli" generate --grind-cmd="bitcoin-util grind" --address=$ADDR --nbits=$NBITS --set-block-time=$(date +%s)
|
||||||
done
|
done
|
||||||
55
miner
55
miner
@ -319,7 +319,7 @@ def seconds_to_hms(s):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson):
|
def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson):
|
||||||
this_interval = 0.000001
|
this_interval = 0.000001
|
||||||
return this_interval
|
return this_interval
|
||||||
|
|
||||||
def next_block_is_mine(last_hash, my_blocks):
|
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)
|
logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine)
|
||||||
mined_blocks += 1
|
mined_blocks += 1
|
||||||
psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time)
|
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):
|
if not psbt_signed.get("complete",False):
|
||||||
logging.debug("Generated PSBT: %s" % (psbt,))
|
logging.debug("Generated PSBT: %s" % (psbt,))
|
||||||
sys.stderr.write("PSBT signing failed\n")
|
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)
|
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 hasattr(args, "address") and hasattr(args, "descriptor"):
|
||||||
if args.address is None and args.descriptor is None:
|
if args.address is None and args.descriptor is None:
|
||||||
sys.stderr.write("Must specify --address or --descriptor\n")
|
sys.stderr.write("Must specify --address or --descriptor\n")
|
||||||
|
|||||||
40
run.sh
40
run.sh
@ -3,8 +3,46 @@
|
|||||||
# run bitcoind
|
# run bitcoind
|
||||||
bitcoind --daemonwait
|
bitcoind --daemonwait
|
||||||
sleep 5
|
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"
|
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)}
|
magic=${magic:(-8)}
|
||||||
echo $magic > /root/.bitcoin/MAGIC.txt
|
echo $magic > /root/.bitcoin/MAGIC.txt
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt)}
|
PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt)}
|
||||||
DATADIR=${DATADIR:-~/.bitcoin/}
|
DATADIR=${DATADIR:-~/.bitcoin/}
|
||||||
bitcoind -datadir=$DATADIR --daemonwait -persistmempool
|
# Note: bitcoind will be started by run.sh, setup-signet.sh just prepares the configuration
|
||||||
bitcoin-cli -datadir=$DATADIR -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=false
|
# The wallet creation and key import will be done in run.sh after bitcoind starts
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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