**Motivations:** - Ajouter dates manquantes dans hash_list.txt et compléter historique - Compléter blockTime manquants dans utxo_list.txt et compléter historique - Récupérer frais depuis transactions d'ancrage (OP_RETURN) et les stocker - Bouton UI pour déclencher récupération frais - Diagnostic Bloc Rewards (pourquoi ~4700 BTC au lieu de 50 BTC) **Root causes:** - hash_list.txt sans date (format ancien) - utxo_list.txt blockTime souvent vide - Frais absents du fichier (métadonnées OP_RETURN non stockées) - Pas de moyen de récupérer/compléter frais depuis UI **Correctifs:** - hash_list.txt : format étendu avec date (rétrocompatible) - utxo_list.txt : blockTime complété automatiquement lors écritures - fees_list.txt : nouveau fichier pour stocker frais - updateFeesFromAnchors() : récupère frais depuis OP_RETURN ancrages - Endpoint /api/utxo/fees/update pour déclencher récupération - Bouton "Récupérer les frais depuis les ancrages" dans section Frais (spinner) - Scripts batch : complete-hash-list-dates.js, complete-utxo-list-blocktime.js - Script diagnostic : diagnose-bloc-rewards.js (subsidy, coinbase, listunspent) **Evolutions:** - Frais chargés depuis fees_list.txt dans getUtxoList - Complétion automatique dates/blockTime lors écritures futures **Pages affectées:** - signet-dashboard/src/bitcoin-rpc.js - signet-dashboard/src/server.js - signet-dashboard/public/utxo-list.html - scripts/complete-hash-list-dates.js - scripts/complete-utxo-list-blocktime.js - scripts/diagnose-bloc-rewards.js - features/utxo-list-fees-update-and-historical-completion.md
158 lines
5.6 KiB
JavaScript
Executable File
158 lines
5.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
/**
|
||
* Script de diagnostic pour comprendre pourquoi les Bloc Rewards affichent ~4700 BTC au lieu de 50 BTC
|
||
* Vérifie : listunspent, getrawtransaction, blockheight, subsidy
|
||
*/
|
||
|
||
import { readFileSync, existsSync } from 'fs';
|
||
import { fileURLToPath } from 'url';
|
||
import { dirname, join } from 'path';
|
||
import { config } from 'dotenv';
|
||
|
||
const require = createRequire(import.meta.url);
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = dirname(__filename);
|
||
|
||
config({ path: join(__dirname, '../signet-dashboard/.env') });
|
||
|
||
const BITCOIN_RPC_URL = process.env.BITCOIN_RPC_URL || 'http://127.0.0.1:38332';
|
||
const BITCOIN_RPC_USER = process.env.BITCOIN_RPC_USER || 'bitcoin';
|
||
const BITCOIN_RPC_PASSWORD = process.env.BITCOIN_RPC_PASSWORD || 'bitcoin';
|
||
const BITCOIN_RPC_WALLET = process.env.BITCOIN_RPC_WALLET || 'custom_signet';
|
||
|
||
const auth = Buffer.from(`${BITCOIN_RPC_USER}:${BITCOIN_RPC_PASSWORD}`).toString('base64');
|
||
const rpcUrl = `${BITCOIN_RPC_URL}/wallet/${BITCOIN_RPC_WALLET}`;
|
||
|
||
async function rpcCall(method, params = []) {
|
||
try {
|
||
const response = await fetch(rpcUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Basic ${auth}`,
|
||
},
|
||
body: JSON.stringify({
|
||
jsonrpc: '1.0',
|
||
id: method,
|
||
method,
|
||
params,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
if (result.error) {
|
||
throw new Error(`RPC error: ${result.error.message}`);
|
||
}
|
||
|
||
return result.result;
|
||
} catch (error) {
|
||
console.error(`Error calling ${method}:`, error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function diagnoseBlocRewards() {
|
||
console.log('🔍 Diagnostic Bloc Rewards - Pourquoi ~4700 BTC au lieu de 50 BTC?\n');
|
||
|
||
// 1. Lire les Bloc Rewards depuis utxo_list.txt
|
||
const utxoListPath = join(__dirname, '../utxo_list.txt');
|
||
if (!existsSync(utxoListPath)) {
|
||
console.error('❌ utxo_list.txt not found');
|
||
return;
|
||
}
|
||
|
||
const content = readFileSync(utxoListPath, 'utf8').trim();
|
||
const lines = content.split('\n');
|
||
const blocRewards = lines
|
||
.filter(line => line.trim().startsWith('bloc_rewards;'))
|
||
.slice(0, 5); // Prendre les 5 premiers
|
||
|
||
if (blocRewards.length === 0) {
|
||
console.log('⚠️ Aucun Bloc Reward trouvé dans utxo_list.txt');
|
||
return;
|
||
}
|
||
|
||
console.log(`📋 Analyse de ${blocRewards.length} Bloc Rewards depuis utxo_list.txt:\n`);
|
||
|
||
// 2. Vérifier blockchain info (hauteur actuelle)
|
||
console.log('1️⃣ Informations blockchain:');
|
||
const blockchainInfo = await rpcCall('getblockchaininfo');
|
||
if (blockchainInfo) {
|
||
console.log(` Hauteur actuelle: ${blockchainInfo.blocks}`);
|
||
console.log(` Chain: ${blockchainInfo.chain}`);
|
||
console.log(` Subsidy attendu (blocs 0-209999): 50 BTC\n`);
|
||
}
|
||
|
||
// 3. Analyser chaque Bloc Reward
|
||
for (let i = 0; i < blocRewards.length; i++) {
|
||
const line = blocRewards[i];
|
||
const parts = line.split(';');
|
||
if (parts.length < 4) continue;
|
||
|
||
const txid = parts[1];
|
||
const amount = parseFloat(parts[3]);
|
||
const confirmations = parseInt(parts[4], 10) || 0;
|
||
|
||
console.log(`\n${i + 1}. Transaction: ${txid}`);
|
||
console.log(` Montant dans fichier: ${amount} BTC`);
|
||
|
||
// 4. Vérifier listunspent
|
||
const unspent = await rpcCall('listunspent', [1]);
|
||
if (unspent) {
|
||
const utxo = unspent.find(u => u.txid === txid && u.vout === 0);
|
||
if (utxo) {
|
||
console.log(` Montant listunspent: ${utxo.amount} BTC`);
|
||
console.log(` Confirmations listunspent: ${utxo.confirmations}`);
|
||
} else {
|
||
console.log(` ⚠️ UTXO non trouvé dans listunspent (peut être dépensé)`);
|
||
}
|
||
}
|
||
|
||
// 5. Vérifier getrawtransaction
|
||
const rawTx = await rpcCall('getrawtransaction', [txid, true]);
|
||
if (rawTx) {
|
||
console.log(` Blockheight: ${rawTx.blockheight || 'non confirmé'}`);
|
||
console.log(` Blocktime: ${rawTx.blocktime ? new Date(rawTx.blocktime * 1000).toISOString() : 'N/A'}`);
|
||
|
||
if (rawTx.vout && rawTx.vout.length > 0) {
|
||
const vout0 = rawTx.vout[0];
|
||
console.log(` Vout[0].value: ${vout0.value} BTC`);
|
||
console.log(` Vout[0].scriptPubKey.type: ${vout0.scriptPubKey?.type || 'N/A'}`);
|
||
|
||
// Vérifier si coinbase
|
||
if (rawTx.vin && rawTx.vin.length === 1 && rawTx.vin[0].coinbase) {
|
||
console.log(` ✅ Transaction coinbase détectée`);
|
||
console.log(` Coinbase: ${rawTx.vin[0].coinbase}`);
|
||
}
|
||
}
|
||
|
||
// 6. Calculer subsidy attendu
|
||
if (rawTx.blockheight !== null && rawTx.blockheight !== undefined) {
|
||
const height = rawTx.blockheight;
|
||
if (height < 210000) {
|
||
const expectedSubsidy = 50;
|
||
const fees = amount - expectedSubsidy;
|
||
console.log(` Subsidy attendu (hauteur ${height}): ${expectedSubsidy} BTC`);
|
||
console.log(` Frais calculés (montant - subsidy): ${fees.toFixed(8)} BTC`);
|
||
if (Math.abs(fees) > 1) {
|
||
console.log(` ⚠️ ÉCART IMPORTANT: ${fees.toFixed(2)} BTC de frais (anormal pour un bloc)`);
|
||
}
|
||
} else {
|
||
console.log(` ⚠️ Bloc après 210000, subsidy devrait être réduit`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('\n\n📝 Conclusion:');
|
||
console.log(' - Si montants listunspent/getrawtransaction = montants fichier → source correcte');
|
||
console.log(' - Si montants >> 50 BTC → signet custom avec subsidy différent ou bug nœud');
|
||
console.log(' - Vérifier chain params du signet (subsidy, halving)');
|
||
}
|
||
|
||
diagnoseBlocRewards().catch(console.error);
|